Introduction

The aim of this report is to develop a predictive model, based on past Airbnb listings in Amsterdam, to help new Airbnb users set competitive prices for their listings. To do so, three different models are considered. These are lasso regularized linear regression, k-nearest-neighbors, and random forest model. The best model is selected based on the predictive performance on the test set. For this purpose, the dataset is split into a training and testing set. Furthermore, each of these models requires model tuning, which is completed using 10-k cross-validation on the training set.

In the following sections, the data cleaning and feature selection process is explained, after which all the three models are tuned and tested. After this, the performance of each model is assessed, and the best model is selected. Finally, the last section discusses the recommendations based on the findings.

Setup notebook

The following libraries are used for this notebook:

# Load libraries
library(tidyverse)
library(tidymodels)
library(glmnet)
library(leaps)
library(naniar)
library(skimr)
library(knitr)
library(corrplot)
library(ranger)
library(doParallel)
library(themis)
library(vip)

Load data listings

# Read csv with listing information
data <- read_csv(gzfile("listings.csv.gz"))

Part 1: Data Cleaning and Feature Selection

Select variables of interest

An important implication for variable selection is to avoid data leakages. You have data leakage when a feature is used for in the predictive models, that at the time of the prediction cannot be available. All variables including information on the reviews provide information about a listing after it has been published. Including this variables could be a potential source of data leakage. Therefore, these variables are not included in the data set. Moreover, the variables including a description and summary about the listing can be analyzed using NLP (e.g. sentiment analysis). However, this is beyond the scope of the assignment. Therefore, these variables are excluded from the model. The variables below are included in the data set for further analysis and cleaning.

# Generate subset with variables of interest
data_sub <- data %>%
  select(id, price, property_type, room_type, accommodates, bathrooms, bedrooms,
         beds, bed_type, amenities, host_since, host_response_time,
         host_response_rate, host_neighbourhood, host_listings_count, 
         host_verifications, host_identity_verified, neighbourhood_cleansed,
         square_feet, cleaning_fee, guests_included, extra_people, 
         minimum_nights, maximum_nights, availability_30, availability_60,
         availability_90, availability_365, instant_bookable, 
         cancellation_policy, require_guest_profile_picture,
         require_guest_phone_verification, calculated_host_listings_count,
         calculated_host_listings_count_entire_homes, 
         calculated_host_listings_count_private_rooms,
         calculated_host_listings_count_shared_rooms)
# Inspect data
head(data_sub)
# Inspect data
skim(data_sub) %>% knit_print()
Name data_sub
Number of rows 20025
Number of columns 36
_______________________
Column type frequency:
character 13
Date 1
logical 4
numeric 18
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
price 0 1.00 5 9 0 479 0
property_type 0 1.00 3 22 0 34 0
room_type 0 1.00 10 15 0 4 0
bed_type 0 1.00 5 13 0 5 0
amenities 0 1.00 2 1303 0 19213 0
host_response_time 158 0.99 3 18 0 5 0
host_response_rate 158 0.99 2 4 0 61 0
host_neighbourhood 5972 0.70 4 35 0 81 0
host_verifications 0 1.00 2 158 0 381 0
neighbourhood_cleansed 0 1.00 4 38 0 22 0
cleaning_fee 3604 0.82 5 7 0 112 0
extra_people 0 1.00 5 7 0 112 0
cancellation_policy 0 1.00 8 27 0 5 0

Variable type: Date

skim_variable n_missing complete_rate min max median n_unique
host_since 158 0.99 2008-09-24 2019-12-06 2015-02-08 3133

Variable type: logical

skim_variable n_missing complete_rate mean count
host_identity_verified 158 0.99 0.39 FAL: 12094, TRU: 7773
instant_bookable 0 1.00 0.26 FAL: 14839, TRU: 5186
require_guest_profile_picture 0 1.00 0.01 FAL: 19819, TRU: 206
require_guest_phone_verification 0 1.00 0.01 FAL: 19758, TRU: 267

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
id 0 1.00 19117026.00 11473019.80 2818 9631819 18418621 27913527.0 40655209 ▇▇▇▅▆
accommodates 0 1.00 2.87 1.30 1 2 2 4.0 18 ▇▁▁▁▁
bathrooms 6 1.00 1.18 0.39 0 1 1 1.5 8 ▇▁▁▁▁
bedrooms 13 1.00 1.45 0.89 0 1 1 2.0 12 ▇▁▁▁▁
beds 31 1.00 1.79 1.41 0 1 1 2.0 32 ▇▁▁▁▁
host_listings_count 158 0.99 3.85 28.93 0 1 1 1.0 751 ▇▁▁▁▁
square_feet 19662 0.02 542.88 560.24 0 0 484 834.0 3229 ▇▅▁▁▁
guests_included 0 1.00 1.46 0.95 1 1 1 2.0 16 ▇▁▁▁▁
minimum_nights 0 1.00 3.43 14.74 1 2 2 3.0 1001 ▇▁▁▁▁
maximum_nights 0 1.00 613.89 548.62 1 20 1125 1125.0 11250 ▇▁▁▁▁
availability_30 0 1.00 4.46 7.81 0 0 0 6.0 30 ▇▁▁▁▁
availability_60 0 1.00 10.05 17.14 0 0 0 13.0 60 ▇▁▁▁▁
availability_90 0 1.00 15.69 26.77 0 0 0 19.0 90 ▇▁▁▁▁
availability_365 0 1.00 47.93 95.34 0 0 0 37.0 365 ▇▁▁▁▁
calculated_host_listings_count 0 1.00 1.97 5.20 1 1 1 1.0 72 ▇▁▁▁▁
calculated_host_listings_count_entire_homes 0 1.00 1.50 5.06 0 1 1 1.0 72 ▇▁▁▁▁
calculated_host_listings_count_private_rooms 0 1.00 0.38 1.08 0 0 0 0.0 16 ▇▁▁▁▁
calculated_host_listings_count_shared_rooms 0 1.00 0.01 0.09 0 0 0 0.0 3 ▇▁▁▁▁

Data cleaning

Basic cleaning

First, we converted all categorical and logical variables, which did not need any further cleaning, to data type factor.

# Convert categorical vars to factors 
data_sub$property_type <- factor(data_sub$property_type , 
                                 levels = unique(data_sub$property_type))
data_sub$room_type <- factor(data_sub$room_type , 
                             levels = unique(data_sub$room_type))
data_sub$bed_type <- factor(data_sub$bed_type , 
                    levels = unique(data_sub$bed_type))
data_sub$host_response_time <- factor(data_sub$ host_response_time, 
                    levels = unique(data_sub$host_response_time))
data_sub$host_neighbourhood <- factor(data_sub$host_neighbourhood, 
                    levels = unique(data_sub$host_neighbourhood))
data_sub$neighbourhood_cleansed <- factor(data_sub$neighbourhood_cleansed, 
                    levels = unique(data_sub$neighbourhood_cleansed))
data_sub$cancellation_policy <- factor(data_sub$cancellation_policy , 
                    levels = unique(data_sub$cancellation_policy))

# Convert logical variables to factors
data_sub$host_identity_verified <- factor(data_sub$host_identity_verified)
data_sub$instant_bookable <- factor(data_sub$instant_bookable)
data_sub$require_guest_profile_picture <- 
  factor(data_sub$require_guest_profile_picture)
data_sub$require_guest_phone_verification <- 
  factor(data_sub$require_guest_phone_verification)

Second, some of the variables contain numeric variables, however, they are stored in a string containing a dollar or percentage sign. The signs were removed from the strings, and the remaining numbers were converted to numeric variables.

# Remove $ sign from columns containing prices and convert to doubles
data_sub$price <- as.double(gsub("[,$]", "", data_sub$price))
data_sub$cleaning_fee <- as.double(gsub("[,$]", "", data_sub$cleaning_fee))
data_sub$extra_people <- as.double(gsub("[,$]", "", data_sub$extra_people))

# Replace "N/A" values, remove % and convert to percentage 
data_sub$host_response_rate <- na_if(data_sub$host_response_rate, "N/A")
data_sub$host_response_rate <- 
  as.double(gsub("[%]", "", data_sub$host_response_rate)) / 100

Clean amenities

The variable amenities contains all the amenities of the listing. However, this was stored in one large string, and the elements could not be accessed separately. Therefore, we cleaned the string and splitted it to a list containing the separated elements. Furthermore, we extracted all unique amenities and stored these in a vector.

# Plot correlation matrix
data_sub %>% select_if(is.numeric) %>% 
  select(availability_30, availability_60, availability_90, availability_365) %>% 
  cor() %>% corrplot()


# Remove other availability variables from data set
data_sub <- 
  data_sub %>% 
  select(-availability_60, -availability_90, -availability_365)

Create new variables based information stored in amenities

Separate variables could be created from the vector containing all unique amenities. However, since there are 130 usable amenities it seemed beyond the scope of the assignment. Moreover, some of the amenities contain information that is also given by other variables or about other amenities. For example a private bathroom could also be a strong indicator that the listing is an entire house/apartment. Also, shampoo or shower gel could be strong indicators for the presence of a bathroom. Furthermore, some amenities are very specific and apply only to a few or one house. Yet, variables are created for some of the amenities that could have an influence on the price of a listing. We created variables for wifi, pool, hot_tub and tv.

# Create variable for WIFI and add to data set
wifi <- vector()
for(i in 1:length(data_sub$amenities_clean)) {
  if("wifi" %in% data_sub$amenities_clean[[i]] | 
     "internet" %in% data_sub$amenities_clean[[i]]) {
    wifi[i] <-  "yes"
  } else {
    wifi[i] <-  "no"
  }
}
data_sub$wifi <- wifi
data_sub$wifi <- factor(data_sub$wifi, levels = c("yes", "no"))

# Create variable for pool and add to data set
pool <- vector()
for(i in 1:length(data_sub$amenities_clean)) {
  if("pool" %in% data_sub$amenities_clean[[i]] |
     "pool with pool hoist" %in% data_sub$amenities_clean[[i]]) {
    pool[i] <-  "yes"
  } else {
    pool[i] <-  "no"
  }
}
data_sub$pool <- pool
data_sub$pool <- factor(data_sub$pool, levels = c("yes", "no"))

# Create variable for hot_tub and add to data set
hot_tub <- vector()
for(i in 1:length(data_sub$amenities_clean)) {
  if("hot tub" %in% data_sub$amenities_clean[[i]]) {
    hot_tub[i] <-  "yes"
  } else {
    hot_tub[i] <-  "no"
  }
}
data_sub$hot_tub <- hot_tub
data_sub$hot_tub <- factor(data_sub$hot_tub, levels = c("yes", "no"))

# Create variable for hot_tub and add to data set
tv <- vector()
for(i in 1:length(data_sub$amenities_clean)) {
  if("tv" %in% data_sub$amenities_clean[[i]] |
     "cable tv" %in% data_sub$amenities_clean[[i]]) {
    tv[i] <-  "yes"
  } else {
    tv[i] <-  "no"
  }
}
data_sub$tv <- tv
data_sub$tv <- factor(data_sub$tv, levels = c("yes", "no"))

Clean host verification methods

The same reasoning from the amenities section above applies to the variable host_verification. The same cleaning method was used as for amenities, first we cleaned and splitted the strings, thereafter a vector with all unique host verification methods is generated.

# Clean and split strings for host verification methods
# Returns a list with all unique values
clean_verficiations <- function(x) {
  subbed <- gsub("\\[|\\]", "", tolower(x))
  subbed_complete <- gsub("[']", "", subbed)
  splitted <- str_split(subbed_complete, ",")
  clean <- sapply(splitted, function(x) str_trim(x, side = "both"))
  return(clean)
}

# Clean host_verifications
data_sub$host_verifications_clean <- 
  sapply(data_sub$host_verifications, function(x) clean_verficiations(x))

# Generate list with all unique host verification methods
verifications_unique = c()
for(verifications in data_sub$host_verifications_clean) {
  for(element in verifications) {
    if(!(element %in% verifications_unique) & element != "") {
      verifications_unique <- append(verifications_unique, str_trim(element))
    }
  }
}
verifications_unique
 [1] "email"                 "phone"                 "reviews"              
 [4] "jumio"                 "offline_government_id" "selfie"               
 [7] "government_id"         "identity_manual"       "facebook"             
[10] "work_email"            "none"                  "google"               
[13] "manual_offline"        "manual_online"         "sent_id"              
[16] "kba"                   "weibo"                 "zhima_selfie"         
[19] "sesame"                "sesame_offline"       

Create variables based on information stored in host verifications

Separate variables are created for the most common methods of verification, namely email, phone, facebook and government_id.

# Create variable for host email and add to data set
host_email <- vector()
for(i in 1:length(data_sub$host_verifications_clean)) {
  if("email" %in% data_sub$host_verifications_clean[[i]]) {
    host_email[i] <-  "yes"
  } else {
    host_email[i] <-  "no"
  }
}
data_sub$host_email <- host_email 
data_sub$host_email <- factor(data_sub$host_email, levels = c("yes", "no"))

# Create variable for phone and add to data set 
host_phone <- vector()
for(i in 1:length(data_sub$host_verifications_clean)) {
  if("phone" %in% data_sub$host_verifications_clean[[i]]) {
    host_phone[i] <-  "yes"
  } else {
    host_phone[i] <-  "no"
  }
}
data_sub$host_phone <- host_phone 
data_sub$host_phone <- factor(data_sub$host_phone, levels = c("yes", "no"))
 
# Create variable for host facebook and add to data set
host_facebook <- vector()
for(i in 1:length(data_sub$host_verifications_clean)) {
  if("facebook" %in% data_sub$host_verifications_clean[[i]]) {
    host_facebook[i] <-  "yes"
  } else {
    host_facebook[i] <-  "no"
  }
}
data_sub$host_facebook <- host_facebook 
data_sub$host_facebook <- 
  factor(data_sub$host_facebook, levels = c("yes", "no"))

# Create variable for government id 
host_government_id <- vector()
for(i in 1:length(data_sub$host_verifications_clean)) {
  if("government_id" %in% data_sub$host_verifications_clean[[i]]) {
    host_government_id[i] <-  "yes"
  } else {
    host_government_id[i] <-  "no"
  }
}
data_sub$host_government_id <- host_government_id 
data_sub$host_government_id <- 
  factor(data_sub$host_government_id, levels = c("yes", "no"))

Clean date variables

We used the variable host_since to create a new variable host_years_active, which contains information regarding the number of years a host has been active on the platform.

# Create new variable for active years host 
data_sub <- data_sub %>% 
  mutate(host_years_active = 
           as.double(as.Date("2019-12-07") - host_since) / 365)

Inspect availability variables

The variables availability_30, availability_60, availability_90 and availability_365 carry some of the same information. In order to inspect if all variables should be included in the data set, we plotted a correlation matrix. The plot below shows that all variables indicating the availability are strongly correlated. Therefore, only the variable availability_30 is included for further analysis.

# Create a train-split sets
seed_x <-  123
set.seed(seed_x)
data_split <- initial_split(data_final, prop = 0.7)
data_train <- training(data_split)
data_test <- testing(data_split)

Check missing data

In order to inspect which variables have missing cases, and how many there are in general, a table is constructed. The table shows the variables in the data set which contain missing values (in descending order). The table shows that the variable square_feet has \(19662\) missing cases, which is about \(98.19\%\). If we would deleted the missing cases, the data set will barely contain any data. Moreover, other methods for handling missing values, like replacing NA-values with the mean or median, would not be appropriate as the variables will be based on only \(1.81\%\) of the data. Therefore, square_feet is not included in the final. The variable host_response_rate has \(9349\) missing cases, which is about \(46.69\%\). The variable host_neighbourhood has \(5972\), which is about \(29.82\%\). The variable cleaning_fee has \(3604\), which is about \(18.00\%\). The variables host_response_rate, host_neighbourhood and cleaning_fee do not have as many missing values as square_feet, however, the same reasoning applies. Due to these findings, these variables are also excluded from the analyses.

# Count missing cases per variable
na_counter <- sapply(data_sub, function(x) sum(is.na(x)))
vars <- colnames(data_sub)

# Extract all variables with NA-values
na_values <- tibble(variables = vars, na_count = na_counter) 

# Check na count per variable
na_values %>%
  filter(na_count > 0) %>%
  arrange(desc(na_count))

Moreover, a check is performed on the number of cases that contain missing values if the other values that have missing cases would be included in the ‘final’ data set. The table below shows that there are 206 cases which contain missing values, which is about \(1.03\%\) of the entire data set. Since this is a very small proportion it is not very likely that deleting these cases would have a large impact on the predictions. Furthermore, all models that will be performed cannot handle missing values. Therefore, the cases with missing values are deleted.

# Compute incomplete rows
data_sub %>% 
  select(host_since, host_response_time, host_listings_count,
         host_identity_verified, beds, bedrooms, host_years_active, 
         bathrooms) %>% 
  complete.cases() %>% 
  summary(count())
   Mode   FALSE    TRUE 
logical     206   19819 
# Select variables for data set
variables_analysis <-   
  na_values %>% 
  filter(na_count <= 158) %>% 
  select(variables) %>% 
  pull(variables)

# Create final data set
data_semi_final <- data_sub %>% select(all_of(variables_analysis))
data_semi_final <- data_semi_final %>% 
  select(-c(amenities, amenities_clean, host_verifications,
            host_verifications_clean, host_since))
data_final <- data_semi_final[complete.cases(data_semi_final), ]

Here is a table describing the features included in the final data set:

Variable Name Variable Description
id Listing ID
price Log transformed price listing (€)
property_type Type of property that is rented out
room_type Type of room that is rented out
accommodates Number of people that can be accommodated in the rented property
bathrooms Number of bathrooms in the property
bedrooms Number of bedrooms in the property
beds Number of beds in the property
bed_type Type of bed present within the property
host_listings_count Number of total listings of a host
host_response_time Average time it takes for a host to respond
host_identity_verified Whether a host verified his identify on Airbnb
neighbourhood_cleansed Neighborhood location of the listed property
guests_included Number of guests included within a listing
extra_people Number of extra people allowed at the listing
minimum_nights The minimum amount of nights that a guest has to book regarding this listing
maximum_nights The maximum amount of nights that a guest is allowed to book regarding this listing
availability_30 The amount of days for which a particular host is available in 30 days
instant_bookable Whether guests can directly book without a inquiry
cancellation_policy The set policy regarding guest cancellation
require_guest_profile_picture Whether the potential guest needs a profile picture in order to book the listing
require_guest_phone_verification Whether the potential guest needs a phone verification in order to book the listing
calculated_host_listings_count The number of total host listings
calculated_host_listings_count_entire_homes The number of total entire home listings of a specific host
calculated_host_listings_count_private_rooms The number of total private room listings of a specific host
calculated_host_listings_count_shared_rooms The number of total shared room listings of a specific host
wifi Whether WiFi is present within the property
pool Whether a pool is present within the property
hot_tub Whether a hot tub is present within the property
tv Whether a tv is present within the property
host_email Whether the host verified his email on Airbnb
host_phone Whether the host verified his phone on Airbnb
host_facebook Whether the host verified his Facebook account on Airbnb
host_government_id Whether the host verified his government ID on Airbnb
host_years_active Number of years the host is active on Airbnb

Create train-test split

For further analysis the data is split into a train-test set.

# Plot distribution price
ggplot(data = data_train , aes(price)) +
  geom_histogram(col="black",
                 breaks=seq(0, max(data_train$price), by=75),
                 aes(fill=..count..)) +
  labs(title="Distribution for Price", x="Price", y="Count") +
  scale_fill_gradient("Count", low="green", high="red") +
  theme(plot.title = element_text(hjust = 0.5))

Inspecting the predicted variable

In order to prevent data leakages we only inspect the predicted variable price in the training set. In order to inspect price we have created a distribution plot. The plot shows that the data set contains some outliers and that the distribution is rightly skewed. Both the outliers and the skeweness make the data less interpretable and this could have an influence on performance of the models.

# Plot distribution price
ggplot(data = data_train , aes(log(price + 1))) +
  geom_histogram(col="black",
                 aes(fill=..count..)) +
  labs(title="Distribution for Ln Price", x="Ln Price", y="Count") +
  scale_fill_gradient("Count", low="green", high="red") +
  theme(plot.title = element_text(hjust = 0.5))

In order to prevent these potential problems we created a new distribution plot with a log transformed variable price. The plot shows that the distribution is less skewed and does not contain many large outliers. Consequently, the data is more interpretable. Therefore, we will use the log transformed price for our models.

# Log transform the price in for both training and test set
data_final$price <- log(data_final$price + 1)

# Resplit the data using the same seed 
set.seed(seed_x)
data_split <- initial_split(data_final, prop = 0.7)
data_train <- training(data_split)
data_test <- testing(data_split)
# Generate 10-fold CV sets
set.seed(321)
data_folds <- vfold_cv(data_train, v = 10)
data_folds
#  10-fold cross-validation 

K-fold cross validation

Moreover, we have generated 10-fold cross validation sets.

rm(data)
rm(data_sub)
rm(data_semi_final)
rm(na_values)

Remove data frames to avoid leakages and errors

#specify the model and engine used
lasso_linreg <- linear_reg(penalty = tune(), mixture = 1) %>% 
  set_engine("glmnet")

#check that model specified correctly:
lasso_linreg %>% translate()
Linear Regression Model Specification (regression)

Main Arguments:
  penalty = tune()
  mixture = 1

Computational engine: glmnet 

Model fit template:
glmnet::glmnet(x = missing_arg(), y = missing_arg(), weights = missing_arg(), 
    alpha = 1, family = "gaussian")

Part 2: Modelling Approaches

Linear Lasso Regularized Regression Model

In this section, a regularized regression model will be specified and trained. A lasso penalty is chosen to simultaneously perform subset selection. Therefore, a mixture is set to 1 in the model specification.

Model specification

Specification of lasso-regularized logistic regression model, where the penalty parameter will be tuned:

#prepare the recipe by setting up the regression model, setting id as id variable, combining small categories to other class, and creating dummies and normalizing variables
lasso_recipe <-  recipe(price ~ ., 
                          data = data_train) %>% 
                        update_role(id, new_role = "ID") %>%
                        step_other(property_type, bed_type,  threshold = 0.01, other = "other values") %>% 
                        step_dummy(all_nominal(), -all_outcomes()) %>%
                        step_normalize(all_predictors(), -all_outcomes())
lasso_recipe

Preprocessing recipe

In this section, the recipe is formulated. All the final dataset variables are included in the recipe, in order to perform the subset selection by means of a lasso penalty. As the property type and bed type have some categories with just a few observations, the categories that include less than 1% of the total number of observations are combined to “other” category to avoid sparse data. Additionally, dummies are created for all of the nominal variables. Lastly, all the variables are normalized.

#prepare and bake the data (on training set) to check that the recipe prepares the data correctly
data_baked <- lasso_recipe %>% prep(data_train) %>% bake(data_train)
head(data_baked)

Testing that this works properly:

#combine the model specification and reciple to a workflow
lasso_wf <- workflow() %>% 
  add_recipe(lasso_recipe) %>% 
  add_model(lasso_linreg)
lasso_wf
== Workflow ====================================================================
Preprocessor: Recipe
Model: linear_reg()

-- Preprocessor ----------------------------------------------------------------
3 Recipe Steps

* step_other()
* step_dummy()
* step_normalize()

-- Model -----------------------------------------------------------------------
Linear Regression Model Specification (regression)

Main Arguments:
  penalty = tune()
  mixture = 1

Computational engine: glmnet 

Create Lasso Workflow

#set tuning grid
grid_lasso <- tibble(penalty = 10^(seq(from = -5, to = 1, length.out = 70)))

Tuning grids

Next, the \(\lambda\) parameter of the lasso model will be tuned. For that purpose, a tuning grid is specified.

 # perform grid search over the tuning grid of penalty values
 lasso_tune <- lasso_wf %>% 
  tune_grid(resamples = data_folds, 
            grid = grid_lasso,
            metrics = metric_set(mae, rmse, rsq_trad))

Tuning lasso-penalized linear regression

10-k-cross-validation is used to tune the lasso-penalized linear regression, and the metrics are plotted against the different values of \(\lambda\).

#save metrics in an object
lasso_tune_metrics <- lasso_tune %>% 
  collect_metrics()

# Plot all results metrics 
lasso_tune_metrics %>%
  ggplot(aes(x = penalty, y = mean, 
             ymin = mean - std_err, ymax = mean + std_err)) + 
  geom_linerange(alpha = 0.5, colour = "red") + 
  geom_point(colour = "red") + 
   facet_wrap(~ .metric, scale = "free_y") +
  scale_x_log10() + 
  labs(y = "Lasso Performance Metrics", x = expression(lambda))


# Plot MAE 
lasso_tune_metrics %>% filter(.metric == "mae") %>% 
  ggplot(aes(x = penalty, y = mean, 
             ymin = mean - std_err, ymax = mean + std_err)) + 
  geom_linerange(alpha = 0.5, colour = "red") + 
  geom_point(colour = "red") + 
  scale_x_log10() + 
  labs(y = "mae", x = expression(lambda), 
       title = "Lasso Regresssion MAE")

 #show best models with corresponding penalty values
lasso_tune %>% show_best("mae")

Next, the Lambda value which results in best model performance on the train set is selected. It can be seen that as the RMSE is more sensitive for large residuals, the standard errors of these metrics are larger compared to the standard errors of mean absolute error (MAE). Therefore, the mean absolute error is used to select the best model.

 #select best model according to 1 std error rule
lasso_1se_model <- select_by_one_std_err(lasso_tune, metric = "mae", desc(penalty))
lasso_1se_model

The best model is selected using the one standard error rule, where the simplest model that has MAE inside one standard error from the absolute best model is chosen to avoid overfitting.

#finalize lasso wf with the selected best model
lasso_wf_tuned <- 
  lasso_wf %>% 
  finalize_workflow(lasso_1se_model)
lasso_wf_tuned
== Workflow ====================================================================
Preprocessor: Recipe
Model: linear_reg()

-- Preprocessor ----------------------------------------------------------------
3 Recipe Steps

* step_other()
* step_dummy()
* step_normalize()

-- Model -----------------------------------------------------------------------
Linear Regression Model Specification (regression)

Main Arguments:
  penalty = 0.00740568469226243
  mixture = 1

Computational engine: glmnet 

As can be seen, the best model has a penalty parameter of 0.007.

Finalize the workflow:

#train the tuned model on all of the train data and test on the test data 
lasso_last_fit <- lasso_wf_tuned %>% 
  last_fit(data_split, metrics = metric_set(mae, rmse, rsq_trad))
#collect metrics from the model on the test set
lasso_test_metrics <- lasso_last_fit %>% collect_metrics()
lasso_test_metrics

The performance on the test set for this model is:

#fit the model on the training data and pull the model coefficients for the variables
lasso_wf_tuned %>% fit(data_train) %>% pull_workflow_fit() %>% tidy() 

As seen above, the final lasso model has mean absolute error of 0.27, root mean squared error of 0.37 and R squared on 48,7% on the test data.

To assess the importance of the predictor variables, model parameter estimates are calculated below:

# Tune specification
rf_tune_spec <- rand_forest(mtry = tune(), trees = 200) %>%
  set_engine("ranger") %>%
  set_mode("regression")

As lasso performs subset selection automatically, some variables have a coefficient of zero. There are multiple variables with coefficient of zero, which implies that these variables are less important for the price prediction of new Airbnb listings. The most important variables can be identified by looking at the coefficients as well, and the 4 most important variables are number of accommodates, the number of days that the airbnb is available inside 30 days, room type of entire home apartment, and lastly, Centrum-West neighbourhood.

Random Forest

Random forest specification

Recipe

Within this section, a random forest will be created. First a preprocessing recipe is created. The id variable is updated to a seperate role, instead of being a predictor.

# Specify recipe
rf_recipe <- recipe(price ~ ., data = data_train) %>%
  update_role(id, new_role = "id var")

rf_recipe
Data Recipe

Inputs:

Tune specifications

Within this section the tune specification are mentioned. The mtry is the number of features that are used at each split. The exact mtry value will be tuned later on. Different values for trees where tested (200, 500 & 1000). Increasing the amount of trees did not have much impact on the results. Therefore, a tree size of 200 is chosen to save computational time.

# Workflow creation
rf_tune_wf <- workflow() %>%
  add_recipe(rf_recipe) %>%
  add_model(rf_tune_spec)

The recipe and the model are combined into a workflow that can be tuned.

registerDoParallel()

A metric set is specified, which calculates the Root Mean Square Error (RMSE), the Mean Absolute Error (MAE) and the R-squared (rsq_trad) is created.

# Class metrics specification 
class_metrics <- metric_set(rmse, mae, rsq_trad)

The command bellow allows us to do computations in parallel.

# Define the tune grid 
rf_tunegrid <- tibble(mtry = c(1:10))

The command grid = tibble(mtry = 1:33) was utlised, as this allows the random forest to consider all number of variables at each split. Based on the MAE criteria, a mtry of 5, 6, 7, 8 & 9 was found as the optimal solution. Afterwards, a mtry of c(1:10)) is taken that will include the optimal values, as well mtry values of 1 up until 4 and a mtry of 10. This allows us to see that the mtry is initially increased up until it reaches its optimal mtry solution.

# Plot variable importance
var_importance_plot <-
  rf_vi_fit %>%
  pull_workflow_fit() %>% vip(geom = "point", num_features = 12) +
  labs(title = "Random Forest Variable Importance") +
  theme(plot.title = element_text(hjust = 0.5)) 
rf_vi_fit
== Workflow [trained] ==========================================================
Preprocessor: Recipe
Model: rand_forest()

-- Preprocessor ----------------------------------------------------------------
0 Recipe Steps

-- Model -----------------------------------------------------------------------
Ranger result

Call:
 ranger::ranger(formula = ..y ~ ., data = data, mtry = ~7, num.trees = ~200,      importance = ~"permutation", num.threads = 1, verbose = FALSE,      seed = sample.int(10^5, 1)) 

Type:                             Regression 
Number of trees:                  200 
Sample size:                      13874 
Number of independent variables:  33 
Mtry:                             7 
Target node size:                 5 
Variable importance mode:         permutation 
Splitrule:                        variance 
OOB prediction error (MSE):       0.1278328 
R squared (OOB):                  0.5472826 
# Save plot for presentation
ggsave("plots/rf_var_importance.png", plot = var_importance_plot,
       height = 7 , width = 10)
# Tune the grid
set.seed(12345)
rf_tune_res <- tune_grid(
  rf_tune_wf,
  resamples = data_folds,
  grid = rf_tunegrid,
  metrics = class_metrics
)
rf_tune_res
# Tuning results
# 10-fold cross-validation 

Selecting tuning parameters

# Collect metrics
rf_tune_res %>%
  collect_metrics()

This plot illustrates the best mtry, based on the criteria of the MAE. A lower MAE would indicate a better result, as a lower value indicates a lower error of prediction.

# Plot results all metrics
rf_tune_res %>%
  collect_metrics() %>%
  filter(.metric %in% c("rmse", "mae", "rsq_trad")) %>%
  ggplot(aes(x = mtry, y = mean, ymin = mean - std_err, ymax = mean + std_err, 
             colour = .metric)) +
  geom_errorbar() + 
  geom_line() +
  geom_point() +
  facet_grid(.metric ~ ., scales = "free_y") +
  labs(title = "Random Forest performance metrics")


# Plot the MAE based
rf_tune_res %>%
  collect_metrics() %>%
  filter(.metric == "mae") %>% 
  ggplot(aes(x = mtry, y = mean, ymin = mean - std_err, ymax = mean + std_err)) +
  geom_errorbar(colour = "red") + 
  geom_line(colour = "red") +
  geom_point(colour = "red") +
  labs(y = "mae", title = "Random Forest performance metrics")

# Generate tuning grid for knn
knn_tune_grid <- tibble(neighbors = 1:50*2-1)

This command will show the best mtry based on the MAE metrics.

# Find the mtry with the best MAE
rf_tune_res %>% show_best("mae")

Best model selection

The best model based on the MAE criteria is selected and eventually finalized into the workflow.

# Best model selection
best_rmse <- select_best(rf_tune_res, "mae")
final_rf <- finalize_workflow(rf_tune_wf, best_rmse)
final_rf
== Workflow ====================================================================
Preprocessor: Recipe
Model: rand_forest()

-- Preprocessor ----------------------------------------------------------------
0 Recipe Steps

-- Model -----------------------------------------------------------------------
Random Forest Model Specification (regression)

Main Arguments:
  mtry = 6
  trees = 200

Computational engine: ranger 

Test set performance

Now we can train the finalized workflow on our entire training set

# Finalise workflow on training set
final_res <- final_rf %>%
  last_fit(data_split, metrics = class_metrics)

The results based on the test set will be

# Score on test data
set.seed(54321)
final_res %>%
  collect_metrics()

Variable importance

Now we try to asses the variable importance. We will refit the model based on our previous tune parameters. We previousyly found an optimal mtry of 7, that’s why the mtry is specified as 7. However, do keep in mind that because of the random element within a random forest, that this initial value might alter. We noticed that the optimal mtry switches between 6, 7 & 8.

# Refit the model
rf_model_vi <- rand_forest(mtry = 7, trees = 200) %>%
  set_engine("ranger", importance = "permutation")

rf_vi_wf <- workflow() %>% 
  add_model(rf_model_vi) %>% 
  add_recipe(rf_recipe)

# Fit the model again
set.seed(12345)
rf_vi_fit <- rf_vi_wf %>% fit(data = data_train)

We can use the refitted model in order the gather the variable importance

# Variable importance 
rf_vi_fit %>% pull_workflow_fit() %>% vi()

The variable importance indicates that the accommodates, bedrooms and room_type are the most important variables for predicting the price. The variables which are the least important for predicting the price are bed_type, pool, and wifi. Pool and wifi actually have a negative importance, but as this is close to a value of 0, it is chosen to still include those variables.

# Specify model 
knn_mod <- 
  nearest_neighbor(neighbors = tune()) %>% 
  set_mode("regression") %>% 
  set_engine("kknn", scale=FALSE)
knn_mod
K-Nearest Neighbor Model Specification (regression)

Main Arguments:
  neighbors = tune()

Engine-Specific Arguments:
  scale = FALSE

Computational engine: kknn 
# Specify recipe
knn_recipe <- 
  recipe(price ~ ., data = data_train) %>% 
  step_rm(all_nominal()) %>%
  update_role(id, new_role = "id var") %>% 
  step_normalize(all_predictors(), -id)
knn_recipe
Data Recipe

Inputs:

Operations:

Delete terms all_nominal()
Centering and scaling for all_predictors(), -id

K-Nearest-Neighbors

Set up turning grid

# Check normalization 
train_baked <- knn_recipe %>% prep(data_train) %>% bake(data_train)
train_baked %>% head()
round(colMeans(train_baked, 8))
                                          id                                 accommodates 
                                    19106945                                            0 
                                   bathrooms                                     bedrooms 
                                           0                                            0 
                                        beds                          host_listings_count 
                                           0                                            0 
                             guests_included                                 extra_people 
                                           0                                            0 
                              minimum_nights                               maximum_nights 
                                           0                                            0 
                             availability_30               calculated_host_listings_count 
                                           0                                            0 
 calculated_host_listings_count_entire_homes calculated_host_listings_count_private_rooms 
                                           0                                            0 
 calculated_host_listings_count_shared_rooms                            host_years_active 
                                           0                                            0 
                                       price 
                                           5 
round(apply(train_baked, 2, sd), 8)
                                          id                                 accommodates 
                                1.141721e+07                                 1.000000e+00 
                                   bathrooms                                     bedrooms 
                                1.000000e+00                                 1.000000e+00 
                                        beds                          host_listings_count 
                                1.000000e+00                                 1.000000e+00 
                             guests_included                                 extra_people 
                                1.000000e+00                                 1.000000e+00 
                              minimum_nights                               maximum_nights 
                                1.000000e+00                                 1.000000e+00 
                             availability_30               calculated_host_listings_count 
                                1.000000e+00                                 1.000000e+00 
 calculated_host_listings_count_entire_homes calculated_host_listings_count_private_rooms 
                                1.000000e+00                                 1.000000e+00 
 calculated_host_listings_count_shared_rooms                            host_years_active 
                                1.000000e+00                                 1.000000e+00 
                                       price 
                                5.313830e-01 
rm(train_baked)

Specify a workflow

Something that should be noted for this recipe is that only numeric variables are included. This is done for the reason that categorical variables translate with difficulty to a k-nearest neighbor algorithm. The premise of prediction based on a KNN-model is that it relies exclusively on the distance between points in the data. This distance is obvious when handling numeric variables. However, when dealing with non-numeric values and variables this distance between data points cannot easily be modeled, provided they should be modeled at all. (This will have implications for determining predictions for importance and coefficients for variables, which will be addressed at the end of the section on the KNN-model).

# Specify workflow
knn_workflow <- 
  workflow() %>% 
  add_model(knn_mod) %>% 
  add_recipe(knn_recipe)
knn_workflow
== Workflow ====================================================================
Preprocessor: Recipe
Model: nearest_neighbor()

-- Preprocessor ----------------------------------------------------------------
2 Recipe Steps

* step_rm()
* step_normalize()

-- Model -----------------------------------------------------------------------
K-Nearest Neighbor Model Specification (regression)

Main Arguments:
  neighbors = tune()

Engine-Specific Arguments:
  scale = FALSE

Computational engine: kknn 

The normalization of the data is ensured through the following commands:

# Store metrics in variable
metrics_reg <- metric_set(rmse, mae, rsq_trad)

# Perform grid search using validation sets
knn_tune_res <- 
  knn_workflow %>% 
  tune_grid(resamples = data_folds,
            grid = knn_tune_grid, 
            metrics = metrics_reg) 

# Plot results metrics
knn_metrics_plot <- 
  knn_tune_res %>%  collect_metrics() %>% 
  ggplot(aes(x = neighbors, y = mean)) +
  geom_point(colour = "red") + geom_line(colour = "red") +
  facet_wrap(~ .metric, scale = "free_y") +
  labs(title = "KNN Performance Metrics") +
  theme(plot.title = element_text(hjust = 0.5)) 
knn_metrics_plot


autoplot(knn_tune_res)

Below the initial workflow for the k-nearest neighbors model is specified

# Generate best model
knn_best_model <- select_best(knn_tune_res, metric = "mae")

Tuning the number of nearest neighbours

The code below serves to specify the assessment metrics that are used. Moreover, a grid search is performed using the validation sets.

# Finalize workflow
knn_workflow_final <- 
  knn_workflow %>% 
  finalize_workflow(knn_best_model)
knn_workflow_final
== Workflow ====================================================================
Preprocessor: Recipe
Model: nearest_neighbor()

-- Preprocessor ----------------------------------------------------------------
2 Recipe Steps

* step_rm()
* step_normalize()

-- Model -----------------------------------------------------------------------
K-Nearest Neighbor Model Specification (regression)

Main Arguments:
  neighbors = 51

Engine-Specific Arguments:
  scale = FALSE

Computational engine: kknn 

The plot output shows some metrics that plot the mean of the performance metrics. We should aim for the MAE (mean absolute error) and RMSE (root mean square error) to be as low as possible, and the rsq_trad (R-squared) to be as high as possible. We used the MAE metric to determine the optimal k-neighbors for our model, which arrived at 51 neighbors. This can be read from the MAE graph, by looking at the corresponsing k-neighbors for the lowest mean of MAE.

Moreover, from the last plot the elbow trend can somewhat clearly be seen: the metrics reach their optimum point after which the level off and slowly increase for MAE and RMSE and decrease for rqs_trad.

The model with the optimal number of k-nearest neighbors can then be selected as follows:

# Train and test the data set
knn_last_fit <- 
  knn_workflow_final %>% 
  last_fit(data_split, 
           metrics = metrics_reg)

Finalize workflow

Below the finalized workflow is made, which automatically picks the best KNN-model defined above (which is specified by the MAE metric)

# Generate predicted values for sales
lasso_test_preds <- 
  lasso_wf_tuned %>% 
  fit(data = data_train) %>%
  predict(data_test) %>% 
  pull(.pred)

# Create tibble for distribution plot
lasso_pred <- 
  tibble(observed = data_test$price, 
         predicted = lasso_test_preds, 
         residual = observed - predicted)

# Plot distribution residuals
lasso_residual_plot <- 
  lasso_pred %>% 
  ggplot(aes(x = residual)) +
  geom_density(bw = 0.15, fill = "springgreen", alpha = 0.5) +
  geom_rug() +
  labs(title = "Lasso Regularized Regression Distribution Residuals") +
  theme(plot.title = element_text(hjust = 0.5)) +
  coord_cartesian(ylim = c(0, 1.5))
lasso_residual_plot


# Generate predicted values for sales
set.seed(12345)
rf_test_preds <- 
  rf_vi_wf %>% 
  fit(data = data_train) %>%
  predict(data_test) %>% 
  pull(.pred)

# Create tibble for distribution plot
rf_pred <- 
  tibble(observed = data_test$price, 
         predicted = rf_test_preds, 
         residual = observed - predicted)

# Plot distribution residuals
rf_residual_plot <- 
  rf_pred %>% 
  ggplot(aes(x = residual)) +
  geom_density(bw = 0.15, fill = "springgreen", alpha = 0.5) +
  geom_rug() +
  labs(title = "Random Forest Distribution Residuals") +
  theme(plot.title = element_text(hjust = 0.5)) +
  coord_cartesian(ylim = c(0, 1.5))
rf_residual_plot


# Generate predicted values for price
knn_test_preds <- 
  knn_workflow_final %>% 
  fit(data = data_train) %>%
  predict(data_test) %>% 
  pull(.pred)

# Create tibble for distribution plot
knn_pred <- 
  tibble(observed = data_test$price, 
         predicted = knn_test_preds, 
         residual = observed - predicted)

# Plot distribution residuals
knn_residual_plot <- 
  knn_pred %>% 
  ggplot(aes(x = residual)) +
  geom_density(bw = 0.15, fill = "springgreen", alpha = 0.5) +
  geom_rug() +
  labs(title = "KNN Distribution Residuals") +
  theme(plot.title = element_text(hjust = 0.5)) +
  coord_cartesian(ylim = c(0, 1.5))
knn_residual_plot

Last fit

A final workflow can be set up to check the final fit. Furthermore, the performance metrics for the best KNN-model are selected and put in a table.

# Generate table with 
lasso_metrics_compare <- 
  lasso_test_metrics %>% 
  select(-.estimator) %>% 
  mutate(model = "lasso regression")
rf_metrics_compare <- 
  final_res %>%
  collect_metrics() %>% 
  mutate(model = "random forest")
knn_metrics_compare <- 
  knn_last_fit %>% 
  collect_metrics %>% 
  select(-.estimator) %>% 
  mutate(model = "knn reg")
lasso_metrics_compare %>%
  bind_rows(rf_metrics_compare, knn_metrics_compare) %>% 
  select(-.estimator) %>% 
  pivot_wider(names_from = .metric, values_from = .estimate)

KNN variable importance

KNN, as a method, does not come with a prediction for the importance or coefficients of variables. The reason for this has to do with the fact that prediction in a k-nearest neighbor model relies exclusively on the distance between data points. With this comes the added implication that no information about the relative importance of variables can be derived from it.

Part 3: Model Comparison

In order to assess the performance of the three models the results of three metrics are compared.

  1. Root mean squared error. The objective is to minimize the result of this metric. An implication is that the metric is very sensitive to observations with large absolute residuals.
  2. Mean absolute error. The objective is to minimize the result of this metric. This metric is more robust and therefore less sensitive to observations with large absolute residuals. However, because it uses the mean, it is therefore not insensitive to skewed distributions.
  3. R-squared. An attraction is that the metric is unitless and can therefore be compared across models. A downside is that is not robust, since it essentially measures correlation and not agreement.

Assess appropriateness metrics

In order to check if the metrics are an appropriate fit, the residuals distributions of the models are plotted. Since the plots below show no large outliers and no skewed distributions, there are no implications for root mean squared error and mean absolute error.

# Generate predicted values for sales
lasso_test_preds <- 
  lasso_wf_tuned %>% 
  fit(data = data_train) %>%
  predict(data_test) %>% 
  pull(.pred)

# Create tibble for distribution plot
lasso_pred <- 
  tibble(observed = data_test$price, 
         predicted = lasso_test_preds, 
         residual = observed - predicted)

# Plot distribution residuals
lasso_residual_plot <- 
  lasso_pred %>% 
  ggplot(aes(x = residual)) +
  geom_density(bw = 0.15, fill = "springgreen", alpha = 0.5) +
  geom_rug() +
  labs(title = "Lasso Regularized Regression Distribution Residuals") +
  theme(plot.title = element_text(hjust = 0.5)) +
  coord_cartesian(ylim = c(0, 1.5))
lasso_residual_plot
# Generate predicted values for sales
set.seed(12345)
rf_test_preds <- 
  rf_vi_wf %>% 
  fit(data = data_train) %>%
  predict(data_test) %>% 
  pull(.pred)

# Create tibble for distribution plot
rf_pred <- 
  tibble(observed = data_test$price, 
         predicted = rf_test_preds, 
         residual = observed - predicted)

# Plot distribution residuals
rf_residual_plot <- 
  rf_pred %>% 
  ggplot(aes(x = residual)) +
  geom_density(bw = 0.15, fill = "springgreen", alpha = 0.5) +
  geom_rug() +
  labs(title = "Random Forest Distribution Residuals") +
  theme(plot.title = element_text(hjust = 0.5)) +
  coord_cartesian(ylim = c(0, 1.5))
rf_residual_plot
# Generate predicted values for price
knn_test_preds <- 
  knn_workflow_final %>% 
  fit(data = data_train) %>%
  predict(data_test) %>% 
  pull(.pred)

# Create tibble for distribution plot
knn_pred <- 
  tibble(observed = data_test$price, 
         predicted = knn_test_preds, 
         residual = observed - predicted)

# Plot distribution residuals
knn_residual_plot <- 
  knn_pred %>% 
  ggplot(aes(x = residual)) +
  geom_density(bw = 0.15, fill = "springgreen", alpha = 0.5) +
  geom_rug() +
  labs(title = "KNN Distribution Residuals") +
  theme(plot.title = element_text(hjust = 0.5)) +
  coord_cartesian(ylim = c(0, 1.5))
knn_residual_plot

Assessment metrics

The table below shows results for the metrics for the three models. When assessing the metrics and their objectives, the results show that the random forest model performs best on all metrics. Therefore, the random forest is considered to be the best model.

# Generate table with 
lasso_metrics_compare <- 
  lasso_test_metrics %>% 
  select(-.estimator) %>% 
  mutate(model = "lasso regression")
rf_metrics_compare <- 
  final_res %>%
  collect_metrics() %>% 
  mutate(model = "random forest")
knn_metrics_compare <- 
  knn_last_fit %>% 
  collect_metrics %>% 
  select(-.estimator) %>% 
  mutate(model = "knn reg")
lasso_metrics_compare %>%
  bind_rows(rf_metrics_compare, knn_metrics_compare) %>% 
  select(-.estimator) %>% 
  pivot_wider(names_from = .metric, values_from = .estimate)

Part 4: Recommendations

For this assignment we assessed three models for predicting prices on the Airbnb platform. These three models are a lasso regularized linear regression, a random forest and finally a K-Nearest Neighbours model. The models are assessed on three different metrics, namely the root mean squared error (RMSE), the mean squared error (MAE) and the r-squared. The proposed assessment metrics indicate that the random forest outperforms the lasso regularized linear regression and the K-Nearest-Neighbor in all proposed metrics.

Furthermore, the variable importance of the random forest indicates which variables lead to the highest increase in R-squared. Accommodates, bedrooms and room_type are the most important variables for predicting the price. Bed_type, pool, and wifi are the least important for predicting the price. We therefore advise hosts to specifically look at the variables with a high importance, as these variables have the highest impact on their predicted price.

As the model performance metrics were used to compare the models and to select the best one, these metrics should not be used to estimate the generalization error, or how well the model would perform on completely new data. For this purpose, it is recommended that the random forest model will be further tested in an online environment before scaling up its implementation. Additionally, in practice, the model will provide the prediction in log prices, and this has to be converted to euros. For example, a log price prediction of 4.09 would reflect price of €59.74.

How are these findings of use to Airbnb? Essentially, this project has sought to examine three approaches to competitively set listing prices of Airbnb listings. Considering our results, we advise Airbnb to use a random forest model in their projects that deal with optimal pricing of listings.

LS0tDQp0aXRsZTogIk1MTEEgLSBHcm91cCBBc3NpZ25tZW50Ig0KYXV0aG9yOg0KLSBHb3N3aW4gTmliYmVyaW5nIC0gNTYzMjg1DQotIEt5bGlhbiB2YW4gTm9vcmRlbm5lIC0gNDUwNzUyDQotIE5vb3JhIE1hdHRzc29uIC0gNDc3MTAwDQotIEVsaW5lIHZhbiBHcm9uaW5nZW4gLSA0MTQ4NDYNCmRhdGU6ICIxMS8yNS8yMDIwIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQoNCiMgSW50cm9kdWN0aW9uDQoNClRoZSBhaW0gb2YgdGhpcyByZXBvcnQgaXMgdG8gZGV2ZWxvcCBhIHByZWRpY3RpdmUgbW9kZWwsIGJhc2VkIG9uIHBhc3QgQWlyYm5iIGxpc3RpbmdzIGluIEFtc3RlcmRhbSwgdG8gaGVscCBuZXcgQWlyYm5iIHVzZXJzIHNldCBjb21wZXRpdGl2ZSBwcmljZXMgZm9yIHRoZWlyIGxpc3RpbmdzLiBUbyBkbyBzbywgdGhyZWUgZGlmZmVyZW50IG1vZGVscyBhcmUgY29uc2lkZXJlZC4gVGhlc2UgYXJlIGxhc3NvIHJlZ3VsYXJpemVkIGxpbmVhciByZWdyZXNzaW9uLCBrLW5lYXJlc3QtbmVpZ2hib3JzLCBhbmQgcmFuZG9tIGZvcmVzdCBtb2RlbC4gVGhlIGJlc3QgbW9kZWwgaXMgc2VsZWN0ZWQgYmFzZWQgb24gdGhlIHByZWRpY3RpdmUgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Qgc2V0LiBGb3IgdGhpcyBwdXJwb3NlLCB0aGUgZGF0YXNldCBpcyBzcGxpdCBpbnRvIGEgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0LiBGdXJ0aGVybW9yZSwgZWFjaCBvZiB0aGVzZSBtb2RlbHMgcmVxdWlyZXMgbW9kZWwgdHVuaW5nLCB3aGljaCBpcyBjb21wbGV0ZWQgdXNpbmcgMTAtayBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQuIA0KDQpJbiB0aGUgZm9sbG93aW5nIHNlY3Rpb25zLCB0aGUgZGF0YSBjbGVhbmluZyBhbmQgZmVhdHVyZSBzZWxlY3Rpb24gcHJvY2VzcyBpcyBleHBsYWluZWQsIGFmdGVyIHdoaWNoIGFsbCB0aGUgdGhyZWUgbW9kZWxzIGFyZSB0dW5lZCBhbmQgdGVzdGVkLiBBZnRlciB0aGlzLCB0aGUgcGVyZm9ybWFuY2Ugb2YgZWFjaCBtb2RlbCBpcyBhc3Nlc3NlZCwgYW5kIHRoZSBiZXN0IG1vZGVsIGlzIHNlbGVjdGVkLiBGaW5hbGx5LCB0aGUgbGFzdCBzZWN0aW9uIGRpc2N1c3NlcyB0aGUgcmVjb21tZW5kYXRpb25zIGJhc2VkIG9uIHRoZSBmaW5kaW5ncy4NCg0KIyBTZXR1cCBub3RlYm9vaw0KDQpUaGUgZm9sbG93aW5nIGxpYnJhcmllcyBhcmUgdXNlZCBmb3IgdGhpcyBub3RlYm9vazoNCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KIyBMb2FkIGxpYnJhcmllcw0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KGdsbW5ldCkNCmxpYnJhcnkobGVhcHMpDQpsaWJyYXJ5KG5hbmlhcikNCmxpYnJhcnkoc2tpbXIpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkocmFuZ2VyKQ0KbGlicmFyeShkb1BhcmFsbGVsKQ0KbGlicmFyeSh0aGVtaXMpDQpsaWJyYXJ5KHZpcCkNCmBgYA0KDQojIExvYWQgZGF0YSBsaXN0aW5ncw0KYGBge3IgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQojIFJlYWQgY3N2IHdpdGggbGlzdGluZyBpbmZvcm1hdGlvbg0KZGF0YSA8LSByZWFkX2NzdihnemZpbGUoImxpc3RpbmdzLmNzdi5neiIpKQ0KYGBgDQoNCiMgUGFydCAxOiBEYXRhIENsZWFuaW5nIGFuZCBGZWF0dXJlIFNlbGVjdGlvbg0KDQojIyBTZWxlY3QgdmFyaWFibGVzIG9mIGludGVyZXN0DQoNCkFuIGltcG9ydGFudCBpbXBsaWNhdGlvbiBmb3IgdmFyaWFibGUgc2VsZWN0aW9uIGlzIHRvIGF2b2lkIGRhdGEgbGVha2FnZXMuIFlvdSBoYXZlIGRhdGEgbGVha2FnZSB3aGVuIGEgZmVhdHVyZSBpcyB1c2VkIGZvciBpbiB0aGUgcHJlZGljdGl2ZSBtb2RlbHMsIHRoYXQgYXQgdGhlIHRpbWUgb2YgdGhlIHByZWRpY3Rpb24gY2Fubm90IGJlIGF2YWlsYWJsZS4gQWxsIHZhcmlhYmxlcyBpbmNsdWRpbmcgaW5mb3JtYXRpb24gb24gdGhlIHJldmlld3MgcHJvdmlkZSBpbmZvcm1hdGlvbiBhYm91dCBhIGxpc3RpbmcgYWZ0ZXIgaXQgaGFzIGJlZW4gcHVibGlzaGVkLiBJbmNsdWRpbmcgdGhpcyB2YXJpYWJsZXMgY291bGQgYmUgYSBwb3RlbnRpYWwgc291cmNlIG9mIGRhdGEgbGVha2FnZS4gVGhlcmVmb3JlLCB0aGVzZSB2YXJpYWJsZXMgYXJlIG5vdCBpbmNsdWRlZCBpbiB0aGUgZGF0YSBzZXQuIE1vcmVvdmVyLCB0aGUgdmFyaWFibGVzIGluY2x1ZGluZyBhIGRlc2NyaXB0aW9uIGFuZCBzdW1tYXJ5IGFib3V0IHRoZSBsaXN0aW5nIGNhbiBiZSBhbmFseXplZCB1c2luZyBOTFAgKGUuZy4gc2VudGltZW50IGFuYWx5c2lzKS4gSG93ZXZlciwgdGhpcyBpcyBiZXlvbmQgdGhlIHNjb3BlIG9mIHRoZSBhc3NpZ25tZW50LiBUaGVyZWZvcmUsIHRoZXNlIHZhcmlhYmxlcyBhcmUgZXhjbHVkZWQgZnJvbSB0aGUgbW9kZWwuIFRoZSB2YXJpYWJsZXMgYmVsb3cgYXJlIGluY2x1ZGVkIGluIHRoZSBkYXRhIHNldCBmb3IgZnVydGhlciBhbmFseXNpcyBhbmQgY2xlYW5pbmcuDQpgYGB7ciBtZXNzYWdlID0gRkFMU0V9DQojIEdlbmVyYXRlIHN1YnNldCB3aXRoIHZhcmlhYmxlcyBvZiBpbnRlcmVzdA0KZGF0YV9zdWIgPC0gZGF0YSAlPiUNCiAgc2VsZWN0KGlkLCBwcmljZSwgcHJvcGVydHlfdHlwZSwgcm9vbV90eXBlLCBhY2NvbW1vZGF0ZXMsIGJhdGhyb29tcywgYmVkcm9vbXMsDQogICAgICAgICBiZWRzLCBiZWRfdHlwZSwgYW1lbml0aWVzLCBob3N0X3NpbmNlLCBob3N0X3Jlc3BvbnNlX3RpbWUsDQogICAgICAgICBob3N0X3Jlc3BvbnNlX3JhdGUsIGhvc3RfbmVpZ2hib3VyaG9vZCwgaG9zdF9saXN0aW5nc19jb3VudCwgDQogICAgICAgICBob3N0X3ZlcmlmaWNhdGlvbnMsIGhvc3RfaWRlbnRpdHlfdmVyaWZpZWQsIG5laWdoYm91cmhvb2RfY2xlYW5zZWQsDQogICAgICAgICBzcXVhcmVfZmVldCwgY2xlYW5pbmdfZmVlLCBndWVzdHNfaW5jbHVkZWQsIGV4dHJhX3Blb3BsZSwgDQogICAgICAgICBtaW5pbXVtX25pZ2h0cywgbWF4aW11bV9uaWdodHMsIGF2YWlsYWJpbGl0eV8zMCwgYXZhaWxhYmlsaXR5XzYwLA0KICAgICAgICAgYXZhaWxhYmlsaXR5XzkwLCBhdmFpbGFiaWxpdHlfMzY1LCBpbnN0YW50X2Jvb2thYmxlLCANCiAgICAgICAgIGNhbmNlbGxhdGlvbl9wb2xpY3ksIHJlcXVpcmVfZ3Vlc3RfcHJvZmlsZV9waWN0dXJlLA0KICAgICAgICAgcmVxdWlyZV9ndWVzdF9waG9uZV92ZXJpZmljYXRpb24sIGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudCwNCiAgICAgICAgIGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudF9lbnRpcmVfaG9tZXMsIA0KICAgICAgICAgY2FsY3VsYXRlZF9ob3N0X2xpc3RpbmdzX2NvdW50X3ByaXZhdGVfcm9vbXMsDQogICAgICAgICBjYWxjdWxhdGVkX2hvc3RfbGlzdGluZ3NfY291bnRfc2hhcmVkX3Jvb21zKQ0KYGBgDQoNCmBgYHtyfQ0KIyBJbnNwZWN0IGRhdGENCmhlYWQoZGF0YV9zdWIpDQpgYGANCg0KYGBge3J9DQojIEluc3BlY3QgZGF0YQ0Kc2tpbShkYXRhX3N1YikgJT4lIGtuaXRfcHJpbnQoKQ0KYGBgDQoNCiMjIERhdGEgY2xlYW5pbmcNCg0KIyMjIEJhc2ljIGNsZWFuaW5nDQoNCkZpcnN0LCB3ZSBjb252ZXJ0ZWQgYWxsIGNhdGVnb3JpY2FsIGFuZCBsb2dpY2FsIHZhcmlhYmxlcywgd2hpY2ggZGlkIG5vdCBuZWVkIGFueSBmdXJ0aGVyIGNsZWFuaW5nLCB0byBkYXRhIHR5cGUgZmFjdG9yLg0KYGBge3IgQ29udmVydGluZyB2ZWN0b3JzfQ0KIyBDb252ZXJ0IGNhdGVnb3JpY2FsIHZhcnMgdG8gZmFjdG9ycyANCmRhdGFfc3ViJHByb3BlcnR5X3R5cGUgPC0gZmFjdG9yKGRhdGFfc3ViJHByb3BlcnR5X3R5cGUgLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHVuaXF1ZShkYXRhX3N1YiRwcm9wZXJ0eV90eXBlKSkNCmRhdGFfc3ViJHJvb21fdHlwZSA8LSBmYWN0b3IoZGF0YV9zdWIkcm9vbV90eXBlICwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHVuaXF1ZShkYXRhX3N1YiRyb29tX3R5cGUpKQ0KZGF0YV9zdWIkYmVkX3R5cGUgPC0gZmFjdG9yKGRhdGFfc3ViJGJlZF90eXBlICwgDQogICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHVuaXF1ZShkYXRhX3N1YiRiZWRfdHlwZSkpDQpkYXRhX3N1YiRob3N0X3Jlc3BvbnNlX3RpbWUgPC0gZmFjdG9yKGRhdGFfc3ViJCBob3N0X3Jlc3BvbnNlX3RpbWUsIA0KICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSB1bmlxdWUoZGF0YV9zdWIkaG9zdF9yZXNwb25zZV90aW1lKSkNCmRhdGFfc3ViJGhvc3RfbmVpZ2hib3VyaG9vZCA8LSBmYWN0b3IoZGF0YV9zdWIkaG9zdF9uZWlnaGJvdXJob29kLCANCiAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gdW5pcXVlKGRhdGFfc3ViJGhvc3RfbmVpZ2hib3VyaG9vZCkpDQpkYXRhX3N1YiRuZWlnaGJvdXJob29kX2NsZWFuc2VkIDwtIGZhY3RvcihkYXRhX3N1YiRuZWlnaGJvdXJob29kX2NsZWFuc2VkLCANCiAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gdW5pcXVlKGRhdGFfc3ViJG5laWdoYm91cmhvb2RfY2xlYW5zZWQpKQ0KZGF0YV9zdWIkY2FuY2VsbGF0aW9uX3BvbGljeSA8LSBmYWN0b3IoZGF0YV9zdWIkY2FuY2VsbGF0aW9uX3BvbGljeSAsIA0KICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSB1bmlxdWUoZGF0YV9zdWIkY2FuY2VsbGF0aW9uX3BvbGljeSkpDQoNCiMgQ29udmVydCBsb2dpY2FsIHZhcmlhYmxlcyB0byBmYWN0b3JzDQpkYXRhX3N1YiRob3N0X2lkZW50aXR5X3ZlcmlmaWVkIDwtIGZhY3RvcihkYXRhX3N1YiRob3N0X2lkZW50aXR5X3ZlcmlmaWVkKQ0KZGF0YV9zdWIkaW5zdGFudF9ib29rYWJsZSA8LSBmYWN0b3IoZGF0YV9zdWIkaW5zdGFudF9ib29rYWJsZSkNCmRhdGFfc3ViJHJlcXVpcmVfZ3Vlc3RfcHJvZmlsZV9waWN0dXJlIDwtIA0KICBmYWN0b3IoZGF0YV9zdWIkcmVxdWlyZV9ndWVzdF9wcm9maWxlX3BpY3R1cmUpDQpkYXRhX3N1YiRyZXF1aXJlX2d1ZXN0X3Bob25lX3ZlcmlmaWNhdGlvbiA8LSANCiAgZmFjdG9yKGRhdGFfc3ViJHJlcXVpcmVfZ3Vlc3RfcGhvbmVfdmVyaWZpY2F0aW9uKQ0KYGBgDQoNClNlY29uZCwgc29tZSBvZiB0aGUgdmFyaWFibGVzIGNvbnRhaW4gbnVtZXJpYyB2YXJpYWJsZXMsIGhvd2V2ZXIsIHRoZXkgYXJlIHN0b3JlZCBpbiBhIHN0cmluZyBjb250YWluaW5nIGEgZG9sbGFyIG9yIHBlcmNlbnRhZ2Ugc2lnbi4gVGhlIHNpZ25zIHdlcmUgcmVtb3ZlZCBmcm9tIHRoZSBzdHJpbmdzLCBhbmQgdGhlIHJlbWFpbmluZyBudW1iZXJzIHdlcmUgY29udmVydGVkIHRvIG51bWVyaWMgdmFyaWFibGVzLg0KYGBge3IgQ29udmVydCB9DQojIFJlbW92ZSAkIHNpZ24gZnJvbSBjb2x1bW5zIGNvbnRhaW5pbmcgcHJpY2VzIGFuZCBjb252ZXJ0IHRvIGRvdWJsZXMNCmRhdGFfc3ViJHByaWNlIDwtIGFzLmRvdWJsZShnc3ViKCJbLCRdIiwgIiIsIGRhdGFfc3ViJHByaWNlKSkNCmRhdGFfc3ViJGNsZWFuaW5nX2ZlZSA8LSBhcy5kb3VibGUoZ3N1YigiWywkXSIsICIiLCBkYXRhX3N1YiRjbGVhbmluZ19mZWUpKQ0KZGF0YV9zdWIkZXh0cmFfcGVvcGxlIDwtIGFzLmRvdWJsZShnc3ViKCJbLCRdIiwgIiIsIGRhdGFfc3ViJGV4dHJhX3Blb3BsZSkpDQoNCiMgUmVwbGFjZSAiTi9BIiB2YWx1ZXMsIHJlbW92ZSAlIGFuZCBjb252ZXJ0IHRvIHBlcmNlbnRhZ2UgDQpkYXRhX3N1YiRob3N0X3Jlc3BvbnNlX3JhdGUgPC0gbmFfaWYoZGF0YV9zdWIkaG9zdF9yZXNwb25zZV9yYXRlLCAiTi9BIikNCmRhdGFfc3ViJGhvc3RfcmVzcG9uc2VfcmF0ZSA8LSANCiAgYXMuZG91YmxlKGdzdWIoIlslXSIsICIiLCBkYXRhX3N1YiRob3N0X3Jlc3BvbnNlX3JhdGUpKSAvIDEwMA0KYGBgDQoNCiMjIyBDbGVhbiBhbWVuaXRpZXMgDQoNClRoZSB2YXJpYWJsZSAqYW1lbml0aWVzKiBjb250YWlucyBhbGwgdGhlIGFtZW5pdGllcyBvZiB0aGUgbGlzdGluZy4gSG93ZXZlciwgdGhpcyB3YXMgc3RvcmVkIGluIG9uZSBsYXJnZSBzdHJpbmcsIGFuZCB0aGUgZWxlbWVudHMgY291bGQgbm90IGJlIGFjY2Vzc2VkIHNlcGFyYXRlbHkuIFRoZXJlZm9yZSwgd2UgY2xlYW5lZCB0aGUgc3RyaW5nIGFuZCBzcGxpdHRlZCBpdCB0byBhIGxpc3QgY29udGFpbmluZyB0aGUgc2VwYXJhdGVkIGVsZW1lbnRzLiBGdXJ0aGVybW9yZSwgd2UgZXh0cmFjdGVkIGFsbCB1bmlxdWUgYW1lbml0aWVzIGFuZCBzdG9yZWQgdGhlc2UgaW4gYSB2ZWN0b3IuIA0KYGBge3J9DQojIENsZWFuIGFuZCBzcGxpdCBzdHJpbmdzIGZvciBhbWVuaXRpZXMNCiMgUmV0dXJucyBhIGxpc3Qgd2l0aCBhbGwgdW5pcXVlIHZhbHVlcw0KY2xlYW5fYW1lbml0aWVzIDwtIGZ1bmN0aW9uKHgpIHsNCiAgc3ViYmVkIDwtIGdzdWIoJ1t7fVwiXScsICIiLCB0b2xvd2VyKHgpKQ0KICBzcGxpdHRlZCA8LSBzdHJfc3BsaXQoc3ViYmVkLCAiLCIpDQogIGNsZWFuIDwtIHNhcHBseShzcGxpdHRlZCwgZnVuY3Rpb24oeCkgc3RyX3RyaW0oeCwgc2lkZSA9ICJib3RoIikpDQogIHJldHVybihjbGVhbikNCn0NCg0KIyBDbGVhbiBhbWVuaXRpZXMNCmRhdGFfc3ViJGFtZW5pdGllc19jbGVhbiA8LSANCiAgc2FwcGx5KGRhdGFfc3ViJGFtZW5pdGllcywgZnVuY3Rpb24oeCkgY2xlYW5fYW1lbml0aWVzKHgpKQ0KDQojIENyZWF0ZSB2ZWN0b3Igd2l0aCBhbGwgdW5pcXVlIGFtZW5pdGllcw0KYW1lbml0aWVzX3VuaXF1ZSA9IGMoKQ0KZm9yKGFtZW5pdGllcyBpbiBkYXRhX3N1YiRhbWVuaXRpZXNfY2xlYW4pIHsNCiAgZm9yKGVsZW1lbnQgaW4gYW1lbml0aWVzKSB7DQogICAgaWYoIShlbGVtZW50ICVpbiUgYW1lbml0aWVzX3VuaXF1ZSkgJiBlbGVtZW50ICE9ICIiKSB7DQogICAgICBhbWVuaXRpZXNfdW5pcXVlIDwtIGFwcGVuZChhbWVuaXRpZXNfdW5pcXVlLCBzdHJfdHJpbShlbGVtZW50KSkNCiAgICB9DQogIH0NCn0NCmFtZW5pdGllc191bmlxdWUNCmBgYA0KDQojIyMjIENyZWF0ZSBuZXcgdmFyaWFibGVzIGJhc2VkIGluZm9ybWF0aW9uIHN0b3JlZCBpbiBhbWVuaXRpZXMNCg0KU2VwYXJhdGUgdmFyaWFibGVzIGNvdWxkIGJlIGNyZWF0ZWQgZnJvbSB0aGUgdmVjdG9yIGNvbnRhaW5pbmcgYWxsIHVuaXF1ZSBhbWVuaXRpZXMuICBIb3dldmVyLCBzaW5jZSB0aGVyZSBhcmUgMTMwIHVzYWJsZSBhbWVuaXRpZXMgaXQgc2VlbWVkIGJleW9uZCB0aGUgc2NvcGUgb2YgdGhlIGFzc2lnbm1lbnQuIE1vcmVvdmVyLCBzb21lIG9mIHRoZSBhbWVuaXRpZXMgY29udGFpbiBpbmZvcm1hdGlvbiB0aGF0IGlzIGFsc28gZ2l2ZW4gYnkgb3RoZXIgdmFyaWFibGVzIG9yIGFib3V0IG90aGVyIGFtZW5pdGllcy4gRm9yIGV4YW1wbGUgYSBwcml2YXRlIGJhdGhyb29tIGNvdWxkIGFsc28gYmUgYSBzdHJvbmcgaW5kaWNhdG9yIHRoYXQgdGhlIGxpc3RpbmcgaXMgYW4gZW50aXJlIGhvdXNlL2FwYXJ0bWVudC4gQWxzbywgc2hhbXBvbyBvciBzaG93ZXIgZ2VsIGNvdWxkIGJlIHN0cm9uZyBpbmRpY2F0b3JzIGZvciB0aGUgcHJlc2VuY2Ugb2YgYSBiYXRocm9vbS4gRnVydGhlcm1vcmUsIHNvbWUgYW1lbml0aWVzIGFyZSB2ZXJ5IHNwZWNpZmljIGFuZCBhcHBseSBvbmx5IHRvIGEgZmV3IG9yIG9uZSBob3VzZS4gWWV0LCB2YXJpYWJsZXMgYXJlIGNyZWF0ZWQgZm9yIHNvbWUgb2YgdGhlIGFtZW5pdGllcyB0aGF0IGNvdWxkIGhhdmUgYW4gaW5mbHVlbmNlIG9uIHRoZSBwcmljZSBvZiBhIGxpc3RpbmcuIFdlIGNyZWF0ZWQgdmFyaWFibGVzIGZvciAqd2lmaSosICpwb29sKiwgKmhvdF90dWIqIGFuZCAqdHYqLiANCmBgYHtyIENyZWF0ZSBuZXcgdmFyaWFibGVzIGZvciBhbWVuaXRpZXN9DQojIENyZWF0ZSB2YXJpYWJsZSBmb3IgV0lGSSBhbmQgYWRkIHRvIGRhdGEgc2V0DQp3aWZpIDwtIHZlY3RvcigpDQpmb3IoaSBpbiAxOmxlbmd0aChkYXRhX3N1YiRhbWVuaXRpZXNfY2xlYW4pKSB7DQogIGlmKCJ3aWZpIiAlaW4lIGRhdGFfc3ViJGFtZW5pdGllc19jbGVhbltbaV1dIHwgDQogICAgICJpbnRlcm5ldCIgJWluJSBkYXRhX3N1YiRhbWVuaXRpZXNfY2xlYW5bW2ldXSkgew0KICAgIHdpZmlbaV0gPC0gICJ5ZXMiDQogIH0gZWxzZSB7DQogICAgd2lmaVtpXSA8LSAgIm5vIg0KICB9DQp9DQpkYXRhX3N1YiR3aWZpIDwtIHdpZmkNCmRhdGFfc3ViJHdpZmkgPC0gZmFjdG9yKGRhdGFfc3ViJHdpZmksIGxldmVscyA9IGMoInllcyIsICJubyIpKQ0KDQojIENyZWF0ZSB2YXJpYWJsZSBmb3IgcG9vbCBhbmQgYWRkIHRvIGRhdGEgc2V0DQpwb29sIDwtIHZlY3RvcigpDQpmb3IoaSBpbiAxOmxlbmd0aChkYXRhX3N1YiRhbWVuaXRpZXNfY2xlYW4pKSB7DQogIGlmKCJwb29sIiAlaW4lIGRhdGFfc3ViJGFtZW5pdGllc19jbGVhbltbaV1dIHwNCiAgICAgInBvb2wgd2l0aCBwb29sIGhvaXN0IiAlaW4lIGRhdGFfc3ViJGFtZW5pdGllc19jbGVhbltbaV1dKSB7DQogICAgcG9vbFtpXSA8LSAgInllcyINCiAgfSBlbHNlIHsNCiAgICBwb29sW2ldIDwtICAibm8iDQogIH0NCn0NCmRhdGFfc3ViJHBvb2wgPC0gcG9vbA0KZGF0YV9zdWIkcG9vbCA8LSBmYWN0b3IoZGF0YV9zdWIkcG9vbCwgbGV2ZWxzID0gYygieWVzIiwgIm5vIikpDQoNCiMgQ3JlYXRlIHZhcmlhYmxlIGZvciBob3RfdHViIGFuZCBhZGQgdG8gZGF0YSBzZXQNCmhvdF90dWIgPC0gdmVjdG9yKCkNCmZvcihpIGluIDE6bGVuZ3RoKGRhdGFfc3ViJGFtZW5pdGllc19jbGVhbikpIHsNCiAgaWYoImhvdCB0dWIiICVpbiUgZGF0YV9zdWIkYW1lbml0aWVzX2NsZWFuW1tpXV0pIHsNCiAgICBob3RfdHViW2ldIDwtICAieWVzIg0KICB9IGVsc2Ugew0KICAgIGhvdF90dWJbaV0gPC0gICJubyINCiAgfQ0KfQ0KZGF0YV9zdWIkaG90X3R1YiA8LSBob3RfdHViDQpkYXRhX3N1YiRob3RfdHViIDwtIGZhY3RvcihkYXRhX3N1YiRob3RfdHViLCBsZXZlbHMgPSBjKCJ5ZXMiLCAibm8iKSkNCg0KIyBDcmVhdGUgdmFyaWFibGUgZm9yIGhvdF90dWIgYW5kIGFkZCB0byBkYXRhIHNldA0KdHYgPC0gdmVjdG9yKCkNCmZvcihpIGluIDE6bGVuZ3RoKGRhdGFfc3ViJGFtZW5pdGllc19jbGVhbikpIHsNCiAgaWYoInR2IiAlaW4lIGRhdGFfc3ViJGFtZW5pdGllc19jbGVhbltbaV1dIHwNCiAgICAgImNhYmxlIHR2IiAlaW4lIGRhdGFfc3ViJGFtZW5pdGllc19jbGVhbltbaV1dKSB7DQogICAgdHZbaV0gPC0gICJ5ZXMiDQogIH0gZWxzZSB7DQogICAgdHZbaV0gPC0gICJubyINCiAgfQ0KfQ0KZGF0YV9zdWIkdHYgPC0gdHYNCmRhdGFfc3ViJHR2IDwtIGZhY3RvcihkYXRhX3N1YiR0diwgbGV2ZWxzID0gYygieWVzIiwgIm5vIikpDQpgYGANCg0KIyMjIENsZWFuIGhvc3QgdmVyaWZpY2F0aW9uIG1ldGhvZHMNCg0KVGhlIHNhbWUgcmVhc29uaW5nIGZyb20gdGhlICphbWVuaXRpZXMqIHNlY3Rpb24gYWJvdmUgYXBwbGllcyB0byB0aGUgdmFyaWFibGUgKmhvc3RfdmVyaWZpY2F0aW9uKi4gVGhlIHNhbWUgY2xlYW5pbmcgbWV0aG9kIHdhcyB1c2VkIGFzIGZvciBhbWVuaXRpZXMsIGZpcnN0IHdlIGNsZWFuZWQgYW5kIHNwbGl0dGVkIHRoZSBzdHJpbmdzLCB0aGVyZWFmdGVyIGEgdmVjdG9yIHdpdGggYWxsIHVuaXF1ZSBob3N0IHZlcmlmaWNhdGlvbiBtZXRob2RzIGlzIGdlbmVyYXRlZC4NCmBgYHtyIEdlbmVyYXRlIHVuaXF1ZSBob3N0IHZlcmlmaWNhdGlvbiBtZXRob2RzfQ0KIyBDbGVhbiBhbmQgc3BsaXQgc3RyaW5ncyBmb3IgaG9zdCB2ZXJpZmljYXRpb24gbWV0aG9kcw0KIyBSZXR1cm5zIGEgbGlzdCB3aXRoIGFsbCB1bmlxdWUgdmFsdWVzDQpjbGVhbl92ZXJmaWNpYXRpb25zIDwtIGZ1bmN0aW9uKHgpIHsNCiAgc3ViYmVkIDwtIGdzdWIoIlxcW3xcXF0iLCAiIiwgdG9sb3dlcih4KSkNCiAgc3ViYmVkX2NvbXBsZXRlIDwtIGdzdWIoIlsnXSIsICIiLCBzdWJiZWQpDQogIHNwbGl0dGVkIDwtIHN0cl9zcGxpdChzdWJiZWRfY29tcGxldGUsICIsIikNCiAgY2xlYW4gPC0gc2FwcGx5KHNwbGl0dGVkLCBmdW5jdGlvbih4KSBzdHJfdHJpbSh4LCBzaWRlID0gImJvdGgiKSkNCiAgcmV0dXJuKGNsZWFuKQ0KfQ0KDQojIENsZWFuIGhvc3RfdmVyaWZpY2F0aW9ucw0KZGF0YV9zdWIkaG9zdF92ZXJpZmljYXRpb25zX2NsZWFuIDwtIA0KICBzYXBwbHkoZGF0YV9zdWIkaG9zdF92ZXJpZmljYXRpb25zLCBmdW5jdGlvbih4KSBjbGVhbl92ZXJmaWNpYXRpb25zKHgpKQ0KDQojIEdlbmVyYXRlIGxpc3Qgd2l0aCBhbGwgdW5pcXVlIGhvc3QgdmVyaWZpY2F0aW9uIG1ldGhvZHMNCnZlcmlmaWNhdGlvbnNfdW5pcXVlID0gYygpDQpmb3IodmVyaWZpY2F0aW9ucyBpbiBkYXRhX3N1YiRob3N0X3ZlcmlmaWNhdGlvbnNfY2xlYW4pIHsNCiAgZm9yKGVsZW1lbnQgaW4gdmVyaWZpY2F0aW9ucykgew0KICAgIGlmKCEoZWxlbWVudCAlaW4lIHZlcmlmaWNhdGlvbnNfdW5pcXVlKSAmIGVsZW1lbnQgIT0gIiIpIHsNCiAgICAgIHZlcmlmaWNhdGlvbnNfdW5pcXVlIDwtIGFwcGVuZCh2ZXJpZmljYXRpb25zX3VuaXF1ZSwgc3RyX3RyaW0oZWxlbWVudCkpDQogICAgfQ0KICB9DQp9DQp2ZXJpZmljYXRpb25zX3VuaXF1ZQ0KYGBgDQoNCiMjIyMgQ3JlYXRlIHZhcmlhYmxlcyBiYXNlZCBvbiBpbmZvcm1hdGlvbiBzdG9yZWQgaW4gaG9zdCB2ZXJpZmljYXRpb25zDQoNClNlcGFyYXRlIHZhcmlhYmxlcyBhcmUgY3JlYXRlZCBmb3IgdGhlIG1vc3QgY29tbW9uIG1ldGhvZHMgb2YgdmVyaWZpY2F0aW9uLCBuYW1lbHkgKmVtYWlsKiwgKnBob25lKiwgKmZhY2Vib29rKiBhbmQgKmdvdmVybm1lbnRfaWQqLiANCmBgYHtyIENyZWF0ZSBuZXcgdmFyaWFibGVzIGZvciBob3N0IHZlcmlmaWNhdGlvbiBtZXRob2R9DQojIENyZWF0ZSB2YXJpYWJsZSBmb3IgaG9zdCBlbWFpbCBhbmQgYWRkIHRvIGRhdGEgc2V0DQpob3N0X2VtYWlsIDwtIHZlY3RvcigpDQpmb3IoaSBpbiAxOmxlbmd0aChkYXRhX3N1YiRob3N0X3ZlcmlmaWNhdGlvbnNfY2xlYW4pKSB7DQogIGlmKCJlbWFpbCIgJWluJSBkYXRhX3N1YiRob3N0X3ZlcmlmaWNhdGlvbnNfY2xlYW5bW2ldXSkgew0KICAgIGhvc3RfZW1haWxbaV0gPC0gICJ5ZXMiDQogIH0gZWxzZSB7DQogICAgaG9zdF9lbWFpbFtpXSA8LSAgIm5vIg0KICB9DQp9DQpkYXRhX3N1YiRob3N0X2VtYWlsIDwtIGhvc3RfZW1haWwgDQpkYXRhX3N1YiRob3N0X2VtYWlsIDwtIGZhY3RvcihkYXRhX3N1YiRob3N0X2VtYWlsLCBsZXZlbHMgPSBjKCJ5ZXMiLCAibm8iKSkNCg0KIyBDcmVhdGUgdmFyaWFibGUgZm9yIHBob25lIGFuZCBhZGQgdG8gZGF0YSBzZXQgDQpob3N0X3Bob25lIDwtIHZlY3RvcigpDQpmb3IoaSBpbiAxOmxlbmd0aChkYXRhX3N1YiRob3N0X3ZlcmlmaWNhdGlvbnNfY2xlYW4pKSB7DQogIGlmKCJwaG9uZSIgJWluJSBkYXRhX3N1YiRob3N0X3ZlcmlmaWNhdGlvbnNfY2xlYW5bW2ldXSkgew0KICAgIGhvc3RfcGhvbmVbaV0gPC0gICJ5ZXMiDQogIH0gZWxzZSB7DQogICAgaG9zdF9waG9uZVtpXSA8LSAgIm5vIg0KICB9DQp9DQpkYXRhX3N1YiRob3N0X3Bob25lIDwtIGhvc3RfcGhvbmUgDQpkYXRhX3N1YiRob3N0X3Bob25lIDwtIGZhY3RvcihkYXRhX3N1YiRob3N0X3Bob25lLCBsZXZlbHMgPSBjKCJ5ZXMiLCAibm8iKSkNCiANCiMgQ3JlYXRlIHZhcmlhYmxlIGZvciBob3N0IGZhY2Vib29rIGFuZCBhZGQgdG8gZGF0YSBzZXQNCmhvc3RfZmFjZWJvb2sgPC0gdmVjdG9yKCkNCmZvcihpIGluIDE6bGVuZ3RoKGRhdGFfc3ViJGhvc3RfdmVyaWZpY2F0aW9uc19jbGVhbikpIHsNCiAgaWYoImZhY2Vib29rIiAlaW4lIGRhdGFfc3ViJGhvc3RfdmVyaWZpY2F0aW9uc19jbGVhbltbaV1dKSB7DQogICAgaG9zdF9mYWNlYm9va1tpXSA8LSAgInllcyINCiAgfSBlbHNlIHsNCiAgICBob3N0X2ZhY2Vib29rW2ldIDwtICAibm8iDQogIH0NCn0NCmRhdGFfc3ViJGhvc3RfZmFjZWJvb2sgPC0gaG9zdF9mYWNlYm9vayANCmRhdGFfc3ViJGhvc3RfZmFjZWJvb2sgPC0gDQogIGZhY3RvcihkYXRhX3N1YiRob3N0X2ZhY2Vib29rLCBsZXZlbHMgPSBjKCJ5ZXMiLCAibm8iKSkNCg0KIyBDcmVhdGUgdmFyaWFibGUgZm9yIGdvdmVybm1lbnQgaWQgDQpob3N0X2dvdmVybm1lbnRfaWQgPC0gdmVjdG9yKCkNCmZvcihpIGluIDE6bGVuZ3RoKGRhdGFfc3ViJGhvc3RfdmVyaWZpY2F0aW9uc19jbGVhbikpIHsNCiAgaWYoImdvdmVybm1lbnRfaWQiICVpbiUgZGF0YV9zdWIkaG9zdF92ZXJpZmljYXRpb25zX2NsZWFuW1tpXV0pIHsNCiAgICBob3N0X2dvdmVybm1lbnRfaWRbaV0gPC0gICJ5ZXMiDQogIH0gZWxzZSB7DQogICAgaG9zdF9nb3Zlcm5tZW50X2lkW2ldIDwtICAibm8iDQogIH0NCn0NCmRhdGFfc3ViJGhvc3RfZ292ZXJubWVudF9pZCA8LSBob3N0X2dvdmVybm1lbnRfaWQgDQpkYXRhX3N1YiRob3N0X2dvdmVybm1lbnRfaWQgPC0gDQogIGZhY3RvcihkYXRhX3N1YiRob3N0X2dvdmVybm1lbnRfaWQsIGxldmVscyA9IGMoInllcyIsICJubyIpKQ0KYGBgDQoNCiMjIyBDbGVhbiBkYXRlIHZhcmlhYmxlcw0KDQpXZSB1c2VkIHRoZSB2YXJpYWJsZSAqaG9zdF9zaW5jZSogdG8gY3JlYXRlIGEgbmV3IHZhcmlhYmxlICpob3N0X3llYXJzX2FjdGl2ZSosIHdoaWNoIGNvbnRhaW5zIGluZm9ybWF0aW9uIHJlZ2FyZGluZyB0aGUgbnVtYmVyIG9mIHllYXJzIGEgaG9zdCBoYXMgYmVlbiBhY3RpdmUgb24gdGhlIHBsYXRmb3JtLiANCmBgYHtyIENyZWF0ZSBuZXcgdmFyaWFibGV9DQojIENyZWF0ZSBuZXcgdmFyaWFibGUgZm9yIGFjdGl2ZSB5ZWFycyBob3N0IA0KZGF0YV9zdWIgPC0gZGF0YV9zdWIgJT4lIA0KICBtdXRhdGUoaG9zdF95ZWFyc19hY3RpdmUgPSANCiAgICAgICAgICAgYXMuZG91YmxlKGFzLkRhdGUoIjIwMTktMTItMDciKSAtIGhvc3Rfc2luY2UpIC8gMzY1KQ0KYGBgDQoNCiMjIyBJbnNwZWN0IGF2YWlsYWJpbGl0eSB2YXJpYWJsZXMNCg0KVGhlIHZhcmlhYmxlcyAqYXZhaWxhYmlsaXR5XzMwKiwgKmF2YWlsYWJpbGl0eV82MCosICphdmFpbGFiaWxpdHlfOTAqIGFuZCAqYXZhaWxhYmlsaXR5XzM2NSogY2Fycnkgc29tZSBvZiB0aGUgc2FtZSBpbmZvcm1hdGlvbi4gSW4gb3JkZXIgdG8gaW5zcGVjdCBpZiBhbGwgdmFyaWFibGVzIHNob3VsZCBiZSBpbmNsdWRlZCBpbiB0aGUgZGF0YSBzZXQsIHdlIHBsb3R0ZWQgYSBjb3JyZWxhdGlvbiBtYXRyaXguIFRoZSBwbG90IGJlbG93IHNob3dzIHRoYXQgYWxsIHZhcmlhYmxlcyBpbmRpY2F0aW5nIHRoZSBhdmFpbGFiaWxpdHkgYXJlIHN0cm9uZ2x5IGNvcnJlbGF0ZWQuIFRoZXJlZm9yZSwgb25seSB0aGUgdmFyaWFibGUgKmF2YWlsYWJpbGl0eV8zMCogaXMgaW5jbHVkZWQgZm9yIGZ1cnRoZXIgYW5hbHlzaXMuIA0KYGBge3J9DQojIFBsb3QgY29ycmVsYXRpb24gbWF0cml4DQpkYXRhX3N1YiAlPiUgc2VsZWN0X2lmKGlzLm51bWVyaWMpICU+JSANCiAgc2VsZWN0KGF2YWlsYWJpbGl0eV8zMCwgYXZhaWxhYmlsaXR5XzYwLCBhdmFpbGFiaWxpdHlfOTAsIGF2YWlsYWJpbGl0eV8zNjUpICU+JSANCiAgY29yKCkgJT4lIGNvcnJwbG90KCkNCg0KIyBSZW1vdmUgb3RoZXIgYXZhaWxhYmlsaXR5IHZhcmlhYmxlcyBmcm9tIGRhdGEgc2V0DQpkYXRhX3N1YiA8LSANCiAgZGF0YV9zdWIgJT4lIA0KICBzZWxlY3QoLWF2YWlsYWJpbGl0eV82MCwgLWF2YWlsYWJpbGl0eV85MCwgLWF2YWlsYWJpbGl0eV8zNjUpIA0KYGBgDQoNCiMjIyBDaGVjayBtaXNzaW5nIGRhdGENCg0KSW4gb3JkZXIgdG8gaW5zcGVjdCB3aGljaCB2YXJpYWJsZXMgaGF2ZSBtaXNzaW5nIGNhc2VzLCBhbmQgaG93IG1hbnkgdGhlcmUgYXJlIGluIGdlbmVyYWwsIGEgdGFibGUgaXMgY29uc3RydWN0ZWQuIFRoZSB0YWJsZSBzaG93cyB0aGUgdmFyaWFibGVzIGluIHRoZSBkYXRhIHNldCB3aGljaCBjb250YWluIG1pc3NpbmcgdmFsdWVzIChpbiBkZXNjZW5kaW5nIG9yZGVyKS4gVGhlIHRhYmxlIHNob3dzIHRoYXQgdGhlIHZhcmlhYmxlICpzcXVhcmVfZmVldCogaGFzICQxOTY2MiQgbWlzc2luZyBjYXNlcywgd2hpY2ggaXMgYWJvdXQgJDk4LjE5XCUkLiBJZiB3ZSB3b3VsZCBkZWxldGVkIHRoZSBtaXNzaW5nIGNhc2VzLCB0aGUgZGF0YSBzZXQgd2lsbCBiYXJlbHkgY29udGFpbiBhbnkgZGF0YS4gTW9yZW92ZXIsIG90aGVyIG1ldGhvZHMgZm9yIGhhbmRsaW5nIG1pc3NpbmcgdmFsdWVzLCBsaWtlIHJlcGxhY2luZyBOQS12YWx1ZXMgd2l0aCB0aGUgbWVhbiBvciBtZWRpYW4sIHdvdWxkIG5vdCBiZSBhcHByb3ByaWF0ZSBhcyB0aGUgdmFyaWFibGVzIHdpbGwgYmUgYmFzZWQgb24gb25seSAkMS44MVwlJCBvZiB0aGUgZGF0YS4gVGhlcmVmb3JlLCAqc3F1YXJlX2ZlZXQqIGlzIG5vdCBpbmNsdWRlZCBpbiB0aGUgZmluYWwuIFRoZSB2YXJpYWJsZSAqaG9zdF9yZXNwb25zZV9yYXRlKiBoYXMgJDkzNDkkIG1pc3NpbmcgY2FzZXMsIHdoaWNoIGlzIGFib3V0ICQ0Ni42OVwlJC4gVGhlIHZhcmlhYmxlICpob3N0X25laWdoYm91cmhvb2QqIGhhcyAkNTk3MiQsIHdoaWNoIGlzIGFib3V0ICQyOS44MlwlJC4gVGhlIHZhcmlhYmxlICpjbGVhbmluZ19mZWUqIGhhcyAkMzYwNCQsIHdoaWNoIGlzIGFib3V0ICQxOC4wMFwlJC4gVGhlIHZhcmlhYmxlcyAgKmhvc3RfcmVzcG9uc2VfcmF0ZSosICpob3N0X25laWdoYm91cmhvb2QqIGFuZCAqY2xlYW5pbmdfZmVlKiBkbyBub3QgaGF2ZSBhcyBtYW55IG1pc3NpbmcgdmFsdWVzIGFzICpzcXVhcmVfZmVldCosIGhvd2V2ZXIsIHRoZSBzYW1lIHJlYXNvbmluZyBhcHBsaWVzLiBEdWUgdG8gdGhlc2UgZmluZGluZ3MsIHRoZXNlIHZhcmlhYmxlcyBhcmUgYWxzbyBleGNsdWRlZCBmcm9tIHRoZSBhbmFseXNlcy4gDQpgYGB7ciBNaXNzaW5nIGRhdGEgY2hlY2t9DQojIENvdW50IG1pc3NpbmcgY2FzZXMgcGVyIHZhcmlhYmxlDQpuYV9jb3VudGVyIDwtIHNhcHBseShkYXRhX3N1YiwgZnVuY3Rpb24oeCkgc3VtKGlzLm5hKHgpKSkNCnZhcnMgPC0gY29sbmFtZXMoZGF0YV9zdWIpDQoNCiMgRXh0cmFjdCBhbGwgdmFyaWFibGVzIHdpdGggTkEtdmFsdWVzDQpuYV92YWx1ZXMgPC0gdGliYmxlKHZhcmlhYmxlcyA9IHZhcnMsIG5hX2NvdW50ID0gbmFfY291bnRlcikgDQoNCiMgQ2hlY2sgbmEgY291bnQgcGVyIHZhcmlhYmxlDQpuYV92YWx1ZXMgJT4lDQogIGZpbHRlcihuYV9jb3VudCA+IDApICU+JQ0KICBhcnJhbmdlKGRlc2MobmFfY291bnQpKQ0KYGBgDQoNCk1vcmVvdmVyLCBhIGNoZWNrIGlzIHBlcmZvcm1lZCBvbiB0aGUgbnVtYmVyIG9mIGNhc2VzIHRoYXQgY29udGFpbiBtaXNzaW5nIHZhbHVlcyBpZiB0aGUgb3RoZXIgdmFsdWVzIHRoYXQgaGF2ZSBtaXNzaW5nIGNhc2VzIHdvdWxkIGJlIGluY2x1ZGVkIGluIHRoZSAnZmluYWwnIGRhdGEgc2V0LiBUaGUgdGFibGUgYmVsb3cgc2hvd3MgdGhhdCB0aGVyZSBhcmUgMjA2IGNhc2VzIHdoaWNoIGNvbnRhaW4gbWlzc2luZyB2YWx1ZXMsIHdoaWNoIGlzIGFib3V0ICQxLjAzXCUkIG9mIHRoZSBlbnRpcmUgZGF0YSBzZXQuIFNpbmNlIHRoaXMgaXMgYSB2ZXJ5IHNtYWxsIHByb3BvcnRpb24gaXQgaXMgbm90IHZlcnkgbGlrZWx5IHRoYXQgZGVsZXRpbmcgdGhlc2UgY2FzZXMgd291bGQgaGF2ZSBhIGxhcmdlIGltcGFjdCBvbiB0aGUgcHJlZGljdGlvbnMuIEZ1cnRoZXJtb3JlLCBhbGwgbW9kZWxzIHRoYXQgd2lsbCBiZSBwZXJmb3JtZWQgY2Fubm90IGhhbmRsZSBtaXNzaW5nIHZhbHVlcy4gVGhlcmVmb3JlLCB0aGUgY2FzZXMgd2l0aCBtaXNzaW5nIHZhbHVlcyBhcmUgZGVsZXRlZC4gDQpgYGB7ciBJbmNvbXBsZXRlIGNhc2VzfQ0KIyBDb21wdXRlIGluY29tcGxldGUgcm93cw0KZGF0YV9zdWIgJT4lIA0KICBzZWxlY3QoaG9zdF9zaW5jZSwgaG9zdF9yZXNwb25zZV90aW1lLCBob3N0X2xpc3RpbmdzX2NvdW50LA0KICAgICAgICAgaG9zdF9pZGVudGl0eV92ZXJpZmllZCwgYmVkcywgYmVkcm9vbXMsIGhvc3RfeWVhcnNfYWN0aXZlLCANCiAgICAgICAgIGJhdGhyb29tcykgJT4lIA0KICBjb21wbGV0ZS5jYXNlcygpICU+JSANCiAgc3VtbWFyeShjb3VudCgpKQ0KYGBgDQoNCmBgYHtyIEdlbmVyYXRlIGZpbmFsIGRhdGEgc2V0fQ0KIyBTZWxlY3QgdmFyaWFibGVzIGZvciBkYXRhIHNldA0KdmFyaWFibGVzX2FuYWx5c2lzIDwtICAgDQogIG5hX3ZhbHVlcyAlPiUgDQogIGZpbHRlcihuYV9jb3VudCA8PSAxNTgpICU+JSANCiAgc2VsZWN0KHZhcmlhYmxlcykgJT4lIA0KICBwdWxsKHZhcmlhYmxlcykNCg0KIyBDcmVhdGUgZmluYWwgZGF0YSBzZXQNCmRhdGFfc2VtaV9maW5hbCA8LSBkYXRhX3N1YiAlPiUgc2VsZWN0KGFsbF9vZih2YXJpYWJsZXNfYW5hbHlzaXMpKQ0KZGF0YV9zZW1pX2ZpbmFsIDwtIGRhdGFfc2VtaV9maW5hbCAlPiUgDQogIHNlbGVjdCgtYyhhbWVuaXRpZXMsIGFtZW5pdGllc19jbGVhbiwgaG9zdF92ZXJpZmljYXRpb25zLA0KICAgICAgICAgICAgaG9zdF92ZXJpZmljYXRpb25zX2NsZWFuLCBob3N0X3NpbmNlKSkNCmRhdGFfZmluYWwgPC0gZGF0YV9zZW1pX2ZpbmFsW2NvbXBsZXRlLmNhc2VzKGRhdGFfc2VtaV9maW5hbCksIF0NCmBgYA0KDQpIZXJlIGlzIGEgdGFibGUgZGVzY3JpYmluZyB0aGUgZmVhdHVyZXMgaW5jbHVkZWQgaW4gdGhlIGZpbmFsIGRhdGEgc2V0Og0KDQp8IFZhcmlhYmxlIE5hbWUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgVmFyaWFibGUgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQp8IGlkICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgTGlzdGluZyBJRA0KfCBwcmljZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IExvZyB0cmFuc2Zvcm1lZCBwcmljZSBsaXN0aW5nICjigqwpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCnwgcHJvcGVydHlfdHlwZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBUeXBlIG9mIHByb3BlcnR5IHRoYXQgaXMgcmVudGVkIG91dA0KfCByb29tX3R5cGUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFR5cGUgb2Ygcm9vbSB0aGF0IGlzIHJlbnRlZCBvdXQNCnwgYWNjb21tb2RhdGVzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBOdW1iZXIgb2YgcGVvcGxlIHRoYXQgY2FuIGJlIGFjY29tbW9kYXRlZCBpbiB0aGUgcmVudGVkIHByb3BlcnR5DQp8IGJhdGhyb29tcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgTnVtYmVyIG9mIGJhdGhyb29tcyBpbiB0aGUgcHJvcGVydHkNCnwgYmVkcm9vbXMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBOdW1iZXIgb2YgYmVkcm9vbXMgaW4gdGhlIHByb3BlcnR5DQp8IGJlZHMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgTnVtYmVyIG9mIGJlZHMgaW4gdGhlIHByb3BlcnR5DQp8IGJlZF90eXBlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgVHlwZSBvZiBiZWQgcHJlc2VudCB3aXRoaW4gdGhlIHByb3BlcnR5DQp8IGhvc3RfbGlzdGluZ3NfY291bnQgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgTnVtYmVyIG9mIHRvdGFsIGxpc3RpbmdzIG9mIGEgaG9zdA0KfCBob3N0X3Jlc3BvbnNlX3RpbWUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IEF2ZXJhZ2UgdGltZSBpdCB0YWtlcyBmb3IgYSBob3N0IHRvIHJlc3BvbmQNCnwgaG9zdF9pZGVudGl0eV92ZXJpZmllZCAgICAgICAgICAgICAgICAgICAgICAgICAgfCBXaGV0aGVyIGEgaG9zdCB2ZXJpZmllZCBoaXMgaWRlbnRpZnkgb24gQWlyYm5iIA0KfCBuZWlnaGJvdXJob29kX2NsZWFuc2VkICAgICAgICAgICAgICAgICAgICAgICAgICB8IE5laWdoYm9yaG9vZCBsb2NhdGlvbiBvZiB0aGUgbGlzdGVkIHByb3BlcnR5DQp8IGd1ZXN0c19pbmNsdWRlZCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgTnVtYmVyIG9mIGd1ZXN0cyBpbmNsdWRlZCB3aXRoaW4gYSBsaXN0aW5nIA0KfCBleHRyYV9wZW9wbGUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IE51bWJlciBvZiBleHRyYSBwZW9wbGUgYWxsb3dlZCBhdCB0aGUgbGlzdGluZw0KfCBtaW5pbXVtX25pZ2h0cyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFRoZSBtaW5pbXVtIGFtb3VudCBvZiBuaWdodHMgdGhhdCBhIGd1ZXN0IGhhcyB0byBib29rIHJlZ2FyZGluZyB0aGlzIGxpc3RpbmcNCnwgbWF4aW11bV9uaWdodHMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBUaGUgbWF4aW11bSBhbW91bnQgb2YgbmlnaHRzIHRoYXQgYSBndWVzdCBpcyBhbGxvd2VkIHRvIGJvb2sgcmVnYXJkaW5nIHRoaXMgbGlzdGluZw0KfCBhdmFpbGFiaWxpdHlfMzAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFRoZSBhbW91bnQgb2YgZGF5cyBmb3Igd2hpY2ggYSBwYXJ0aWN1bGFyIGhvc3QgaXMgYXZhaWxhYmxlIGluIDMwIGRheXMNCnwgaW5zdGFudF9ib29rYWJsZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBXaGV0aGVyIGd1ZXN0cyBjYW4gZGlyZWN0bHkgYm9vayB3aXRob3V0IGEgaW5xdWlyeQ0KfCBjYW5jZWxsYXRpb25fcG9saWN5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFRoZSBzZXQgcG9saWN5IHJlZ2FyZGluZyBndWVzdCBjYW5jZWxsYXRpb24gDQp8IHJlcXVpcmVfZ3Vlc3RfcHJvZmlsZV9waWN0dXJlICAgICAgICAgICAgICAgICAgIHwgV2hldGhlciB0aGUgcG90ZW50aWFsIGd1ZXN0IG5lZWRzIGEgcHJvZmlsZSBwaWN0dXJlIGluIG9yZGVyIHRvIGJvb2sgdGhlIGxpc3RpbmcNCnwgcmVxdWlyZV9ndWVzdF9waG9uZV92ZXJpZmljYXRpb24gICAgICAgICAgICAgICAgfCBXaGV0aGVyIHRoZSBwb3RlbnRpYWwgZ3Vlc3QgbmVlZHMgYSBwaG9uZSB2ZXJpZmljYXRpb24gaW4gb3JkZXIgdG8gYm9vayB0aGUgbGlzdGluZw0KfCBjYWxjdWxhdGVkX2hvc3RfbGlzdGluZ3NfY291bnQgICAgICAgICAgICAgICAgICB8IFRoZSBudW1iZXIgb2YgdG90YWwgaG9zdCBsaXN0aW5ncw0KfCBjYWxjdWxhdGVkX2hvc3RfbGlzdGluZ3NfY291bnRfZW50aXJlX2hvbWVzICAgICB8IFRoZSBudW1iZXIgb2YgdG90YWwgZW50aXJlIGhvbWUgbGlzdGluZ3Mgb2YgYSBzcGVjaWZpYyBob3N0DQp8IGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudF9wcml2YXRlX3Jvb21zICAgIHwgVGhlIG51bWJlciBvZiB0b3RhbCBwcml2YXRlIHJvb20gbGlzdGluZ3Mgb2YgYSBzcGVjaWZpYyBob3N0DQp8IGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudF9zaGFyZWRfcm9vbXMgICAgIHwgVGhlIG51bWJlciBvZiB0b3RhbCBzaGFyZWQgcm9vbSBsaXN0aW5ncyBvZiBhIHNwZWNpZmljIGhvc3QNCnwgd2lmaSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBXaGV0aGVyIFdpRmkgaXMgcHJlc2VudCB3aXRoaW4gdGhlIHByb3BlcnR5DQp8IHBvb2wgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgV2hldGhlciBhIHBvb2wgaXMgcHJlc2VudCB3aXRoaW4gdGhlIHByb3BlcnR5IA0KfCBob3RfdHViICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFdoZXRoZXIgYSBob3QgdHViIGlzIHByZXNlbnQgd2l0aGluIHRoZSBwcm9wZXJ0eQ0KfCB0diAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFdoZXRoZXIgYSB0diBpcyBwcmVzZW50IHdpdGhpbiB0aGUgcHJvcGVydHkgDQp8IGhvc3RfZW1haWwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgV2hldGhlciB0aGUgaG9zdCB2ZXJpZmllZCBoaXMgZW1haWwgb24gQWlyYm5iDQp8IGhvc3RfcGhvbmUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgV2hldGhlciB0aGUgaG9zdCB2ZXJpZmllZCBoaXMgcGhvbmUgb24gQWlyYm5iDQp8IGhvc3RfZmFjZWJvb2sgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgV2hldGhlciB0aGUgaG9zdCB2ZXJpZmllZCBoaXMgRmFjZWJvb2sgYWNjb3VudCBvbiBBaXJibmINCnwgaG9zdF9nb3Zlcm5tZW50X2lkICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBXaGV0aGVyIHRoZSBob3N0IHZlcmlmaWVkIGhpcyBnb3Zlcm5tZW50IElEIG9uIEFpcmJuYg0KfCBob3N0X3llYXJzX2FjdGl2ZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IE51bWJlciBvZiB5ZWFycyB0aGUgaG9zdCBpcyBhY3RpdmUgb24gQWlyYm5iDQoNCg0KIyMgQ3JlYXRlIHRyYWluLXRlc3Qgc3BsaXQNCg0KRm9yIGZ1cnRoZXIgYW5hbHlzaXMgdGhlIGRhdGEgaXMgc3BsaXQgaW50byBhIHRyYWluLXRlc3Qgc2V0Lg0KYGBge3J9DQojIENyZWF0ZSBhIHRyYWluLXNwbGl0IHNldHMNCnNlZWRfeCA8LSAgMTIzDQpzZXQuc2VlZChzZWVkX3gpDQpkYXRhX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoZGF0YV9maW5hbCwgcHJvcCA9IDAuNykNCmRhdGFfdHJhaW4gPC0gdHJhaW5pbmcoZGF0YV9zcGxpdCkNCmRhdGFfdGVzdCA8LSB0ZXN0aW5nKGRhdGFfc3BsaXQpDQpgYGANCg0KIyMjIEluc3BlY3RpbmcgdGhlIHByZWRpY3RlZCB2YXJpYWJsZQ0KDQpJbiBvcmRlciB0byBwcmV2ZW50IGRhdGEgbGVha2FnZXMgd2Ugb25seSBpbnNwZWN0IHRoZSBwcmVkaWN0ZWQgdmFyaWFibGUgKnByaWNlKiBpbiB0aGUgdHJhaW5pbmcgc2V0LiBJbiBvcmRlciB0byBpbnNwZWN0ICpwcmljZSogd2UgaGF2ZSBjcmVhdGVkIGEgZGlzdHJpYnV0aW9uIHBsb3QuIFRoZSBwbG90IHNob3dzIHRoYXQgdGhlIGRhdGEgc2V0IGNvbnRhaW5zIHNvbWUgb3V0bGllcnMgYW5kIHRoYXQgdGhlIGRpc3RyaWJ1dGlvbiBpcyByaWdodGx5IHNrZXdlZC4gQm90aCB0aGUgb3V0bGllcnMgYW5kIHRoZSBza2V3ZW5lc3MgbWFrZSB0aGUgZGF0YSBsZXNzIGludGVycHJldGFibGUgYW5kIHRoaXMgY291bGQgaGF2ZSBhbiBpbmZsdWVuY2Ugb24gcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVscy4gDQpgYGB7cn0NCiMgUGxvdCBkaXN0cmlidXRpb24gcHJpY2UNCmdncGxvdChkYXRhID0gZGF0YV90cmFpbiAsIGFlcyhwcmljZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oY29sPSJibGFjayIsDQogICAgICAgICAgICAgICAgIGJyZWFrcz1zZXEoMCwgbWF4KGRhdGFfdHJhaW4kcHJpY2UpLCBieT03NSksDQogICAgICAgICAgICAgICAgIGFlcyhmaWxsPS4uY291bnQuLikpICsNCiAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIGZvciBQcmljZSIsIHg9IlByaWNlIiwgeT0iQ291bnQiKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQoIkNvdW50IiwgbG93PSJncmVlbiIsIGhpZ2g9InJlZCIpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQpgYGANCg0KSW4gb3JkZXIgdG8gcHJldmVudCB0aGVzZSBwb3RlbnRpYWwgcHJvYmxlbXMgd2UgY3JlYXRlZCBhIG5ldyBkaXN0cmlidXRpb24gcGxvdCB3aXRoIGEgbG9nIHRyYW5zZm9ybWVkIHZhcmlhYmxlICpwcmljZSouIFRoZSBwbG90IHNob3dzIHRoYXQgdGhlIGRpc3RyaWJ1dGlvbiBpcyBsZXNzIHNrZXdlZCBhbmQgZG9lcyBub3QgY29udGFpbiBtYW55IGxhcmdlIG91dGxpZXJzLiBDb25zZXF1ZW50bHksIHRoZSBkYXRhIGlzIG1vcmUgaW50ZXJwcmV0YWJsZS4gVGhlcmVmb3JlLCB3ZSB3aWxsIHVzZSB0aGUgbG9nIHRyYW5zZm9ybWVkICpwcmljZSogZm9yIG91ciBtb2RlbHMuIA0KYGBge3J9DQojIFBsb3QgZGlzdHJpYnV0aW9uIHByaWNlDQpnZ3Bsb3QoZGF0YSA9IGRhdGFfdHJhaW4gLCBhZXMobG9nKHByaWNlICsgMSkpKSArDQogIGdlb21faGlzdG9ncmFtKGNvbD0iYmxhY2siLA0KICAgICAgICAgICAgICAgICBhZXMoZmlsbD0uLmNvdW50Li4pKSArDQogIGxhYnModGl0bGU9IkRpc3RyaWJ1dGlvbiBmb3IgTG4gUHJpY2UiLCB4PSJMbiBQcmljZSIsIHk9IkNvdW50IikgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KCJDb3VudCIsIGxvdz0iZ3JlZW4iLCBoaWdoPSJyZWQiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQ0KYGBgDQoNCmBgYHtyfQ0KIyBMb2cgdHJhbnNmb3JtIHRoZSBwcmljZSBpbiBmb3IgYm90aCB0cmFpbmluZyBhbmQgdGVzdCBzZXQNCmRhdGFfZmluYWwkcHJpY2UgPC0gbG9nKGRhdGFfZmluYWwkcHJpY2UgKyAxKQ0KDQojIFJlc3BsaXQgdGhlIGRhdGEgdXNpbmcgdGhlIHNhbWUgc2VlZCANCnNldC5zZWVkKHNlZWRfeCkNCmRhdGFfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChkYXRhX2ZpbmFsLCBwcm9wID0gMC43KQ0KZGF0YV90cmFpbiA8LSB0cmFpbmluZyhkYXRhX3NwbGl0KQ0KZGF0YV90ZXN0IDwtIHRlc3RpbmcoZGF0YV9zcGxpdCkNCg0KYGBgDQoNCiMjIyBLLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiANCg0KTW9yZW92ZXIsIHdlIGhhdmUgZ2VuZXJhdGVkIDEwLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBzZXRzLg0KYGBge3J9DQojIEdlbmVyYXRlIDEwLWZvbGQgQ1Ygc2V0cw0Kc2V0LnNlZWQoMzIxKQ0KZGF0YV9mb2xkcyA8LSB2Zm9sZF9jdihkYXRhX3RyYWluLCB2ID0gMTApDQpkYXRhX2ZvbGRzDQpgYGANCg0KIyMjIFJlbW92ZSBkYXRhIGZyYW1lcyB0byBhdm9pZCBsZWFrYWdlcyBhbmQgZXJyb3JzDQpgYGB7cn0NCnJtKGRhdGEpDQpybShkYXRhX3N1YikNCnJtKGRhdGFfc2VtaV9maW5hbCkNCnJtKG5hX3ZhbHVlcykNCmBgYA0KDQojIFBhcnQgMjogTW9kZWxsaW5nIEFwcHJvYWNoZXMNCg0KIyMgTGluZWFyIExhc3NvIFJlZ3VsYXJpemVkIFJlZ3Jlc3Npb24gTW9kZWwNCg0KSW4gdGhpcyBzZWN0aW9uLCBhIHJlZ3VsYXJpemVkIHJlZ3Jlc3Npb24gbW9kZWwgd2lsbCBiZSBzcGVjaWZpZWQgYW5kIHRyYWluZWQuIEEgbGFzc28gcGVuYWx0eSBpcyBjaG9zZW4gdG8gc2ltdWx0YW5lb3VzbHkgcGVyZm9ybSBzdWJzZXQgc2VsZWN0aW9uLiBUaGVyZWZvcmUsIGEgbWl4dHVyZSBpcyBzZXQgdG8gMSBpbiB0aGUgbW9kZWwgc3BlY2lmaWNhdGlvbi4gDQoNCiMjIyBNb2RlbCBzcGVjaWZpY2F0aW9uDQoNClNwZWNpZmljYXRpb24gb2YgbGFzc28tcmVndWxhcml6ZWQgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCwgd2hlcmUgdGhlIHBlbmFsdHkgcGFyYW1ldGVyIHdpbGwgYmUgdHVuZWQ6DQpgYGB7ciByZXN1bHRzPSdoaWRlJ30NCiMgU3BlY2lmeSB0aGUgbW9kZWwgYW5kIGVuZ2luZSB1c2VkDQpsYXNzb19saW5yZWcgPC0gbGluZWFyX3JlZyhwZW5hbHR5ID0gdHVuZSgpLCBtaXh0dXJlID0gMSkgJT4lIA0KICBzZXRfZW5naW5lKCJnbG1uZXQiKQ0KDQojIENoZWNrIHRoYXQgbW9kZWwgc3BlY2lmaWVkIGNvcnJlY3RseToNCmxhc3NvX2xpbnJlZyAlPiUgdHJhbnNsYXRlKCkNCmBgYA0KDQojIyMgUHJlcHJvY2Vzc2luZyByZWNpcGUNCg0KSW4gdGhpcyBzZWN0aW9uLCB0aGUgcmVjaXBlIGlzIGZvcm11bGF0ZWQuIEFsbCB0aGUgZmluYWwgZGF0YXNldCB2YXJpYWJsZXMgYXJlIGluY2x1ZGVkIGluIHRoZSByZWNpcGUsIGluIG9yZGVyIHRvIHBlcmZvcm0gdGhlIHN1YnNldCBzZWxlY3Rpb24gYnkgbWVhbnMgb2YgYSBsYXNzbyBwZW5hbHR5LiBBcyB0aGUgcHJvcGVydHkgdHlwZSBhbmQgYmVkIHR5cGUgaGF2ZSBzb21lIGNhdGVnb3JpZXMgd2l0aCBqdXN0IGEgZmV3IG9ic2VydmF0aW9ucywgdGhlIGNhdGVnb3JpZXMgdGhhdCBpbmNsdWRlIGxlc3MgdGhhbiAxJSBvZiB0aGUgdG90YWwgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBhcmUgY29tYmluZWQgdG8gIm90aGVyIiBjYXRlZ29yeSB0byBhdm9pZCBzcGFyc2UgZGF0YS4gQWRkaXRpb25hbGx5LCBkdW1taWVzIGFyZSBjcmVhdGVkIGZvciBhbGwgb2YgdGhlIG5vbWluYWwgdmFyaWFibGVzLiBMYXN0bHksIGFsbCB0aGUgdmFyaWFibGVzIGFyZSBub3JtYWxpemVkLg0KYGBge3IgcmVzdWx0cz0naGlkZSd9DQojIFByZXBhcmUgdGhlIHJlY2lwZSBieSBzZXR0aW5nIHVwIHRoZSByZWdyZXNzaW9uIG1vZGVsLCBzZXR0aW5nIGlkIGFzIGlkIHZhcmlhYmxlLCBjb21iaW5pbmcgc21hbGwgY2F0ZWdvcmllcyB0byBvdGhlciBjbGFzcywgYW5kIGNyZWF0aW5nIGR1bW1pZXMgYW5kIG5vcm1hbGl6aW5nIHZhcmlhYmxlcw0KbGFzc29fcmVjaXBlIDwtICByZWNpcGUocHJpY2UgfiAuLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfdHJhaW4pICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgIHVwZGF0ZV9yb2xlKGlkLCBuZXdfcm9sZSA9ICJJRCIpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgc3RlcF9vdGhlcihwcm9wZXJ0eV90eXBlLCBiZWRfdHlwZSwgIHRocmVzaG9sZCA9IDAuMDEsIG90aGVyID0gIm90aGVyIHZhbHVlcyIpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgIHN0ZXBfbm9ybWFsaXplKGFsbF9wcmVkaWN0b3JzKCksIC1hbGxfb3V0Y29tZXMoKSkNCmxhc3NvX3JlY2lwZQ0KYGBgDQoNClRlc3RpbmcgdGhhdCB0aGlzIHdvcmtzIHByb3Blcmx5Og0KYGBge3J9DQojIFByZXBhcmUgYW5kIGJha2UgdGhlIGRhdGEgKG9uIHRyYWluaW5nIHNldCkgdG8gY2hlY2sgdGhhdCB0aGUgcmVjaXBlIHByZXBhcmVzIHRoZSBkYXRhIGNvcnJlY3RseQ0KZGF0YV9iYWtlZCA8LSBsYXNzb19yZWNpcGUgJT4lIHByZXAoZGF0YV90cmFpbikgJT4lIGJha2UoZGF0YV90cmFpbikNCmhlYWQoZGF0YV9iYWtlZCkNCmBgYA0KDQojIyMgQ3JlYXRlIExhc3NvIFdvcmtmbG93DQpgYGB7ciByZXN1bHRzPSdoaWRlJ30NCiMgQ29tYmluZSB0aGUgbW9kZWwgc3BlY2lmaWNhdGlvbiBhbmQgcmVjaXBsZSB0byBhIHdvcmtmbG93DQpsYXNzb193ZiA8LSB3b3JrZmxvdygpICU+JSANCiAgYWRkX3JlY2lwZShsYXNzb19yZWNpcGUpICU+JSANCiAgYWRkX21vZGVsKGxhc3NvX2xpbnJlZykNCmxhc3NvX3dmDQpgYGANCg0KIyMjIFR1bmluZyBncmlkcw0KTmV4dCwgdGhlICRcbGFtYmRhJCBwYXJhbWV0ZXIgb2YgdGhlIGxhc3NvIG1vZGVsIHdpbGwgYmUgdHVuZWQuIEZvciB0aGF0IHB1cnBvc2UsIGEgdHVuaW5nIGdyaWQgaXMgc3BlY2lmaWVkLiANCmBgYHtyfQ0KIyBTZXQgdHVuaW5nIGdyaWQNCmdyaWRfbGFzc28gPC0gdGliYmxlKHBlbmFsdHkgPSAxMF4oc2VxKGZyb20gPSAtNSwgdG8gPSAxLCBsZW5ndGgub3V0ID0gNzApKSkNCmBgYA0KDQojIyMgVHVuaW5nIGxhc3NvLXBlbmFsaXplZCBsaW5lYXIgcmVncmVzc2lvbg0KMTAtay1jcm9zcy12YWxpZGF0aW9uIGlzIHVzZWQgdG8gdHVuZSB0aGUgbGFzc28tcGVuYWxpemVkIGxpbmVhciByZWdyZXNzaW9uLCBhbmQgdGhlIG1ldHJpY3MgYXJlIHBsb3R0ZWQgYWdhaW5zdCB0aGUgZGlmZmVyZW50IHZhbHVlcyBvZiAkXGxhbWJkYSQuDQpgYGB7cn0NCiAjIFBlcmZvcm0gZ3JpZCBzZWFyY2ggb3ZlciB0aGUgdHVuaW5nIGdyaWQgb2YgcGVuYWx0eSB2YWx1ZXMNCiBsYXNzb190dW5lIDwtIGxhc3NvX3dmICU+JSANCiAgdHVuZV9ncmlkKHJlc2FtcGxlcyA9IGRhdGFfZm9sZHMsIA0KICAgICAgICAgICAgZ3JpZCA9IGdyaWRfbGFzc28sDQogICAgICAgICAgICBtZXRyaWNzID0gbWV0cmljX3NldChtYWUsIHJtc2UsIHJzcV90cmFkKSkNCmBgYA0KDQpgYGB7ciBtZXNzYWdlPVRSVUV9DQojIFN0b3JlIG1ldHJpY3MgaW4gdmFyaWFibGUNCmxhc3NvX3R1bmVfbWV0cmljcyA8LSBsYXNzb190dW5lICU+JSANCiAgY29sbGVjdF9tZXRyaWNzKCkNCg0KIyBQbG90IGFsbCByZXN1bHRzIG1ldHJpY3MgDQpsYXNzb190dW5lX21ldHJpY3MgJT4lDQogIGdncGxvdChhZXMoeCA9IHBlbmFsdHksIHkgPSBtZWFuLCANCiAgICAgICAgICAgICB5bWluID0gbWVhbiAtIHN0ZF9lcnIsIHltYXggPSBtZWFuICsgc3RkX2VycikpICsgDQogIGdlb21fbGluZXJhbmdlKGFscGhhID0gMC41LCBjb2xvdXIgPSAicmVkIikgKyANCiAgZ2VvbV9wb2ludChjb2xvdXIgPSAicmVkIikgKyANCiAgIGZhY2V0X3dyYXAofiAubWV0cmljLCBzY2FsZSA9ICJmcmVlX3kiKSArDQogIHNjYWxlX3hfbG9nMTAoKSArIA0KICBsYWJzKHkgPSAiTGFzc28gUGVyZm9ybWFuY2UgTWV0cmljcyIsIHggPSBleHByZXNzaW9uKGxhbWJkYSkpDQoNCiMgUGxvdCBNQUUgDQpsYXNzb190dW5lX21ldHJpY3MgJT4lIGZpbHRlcigubWV0cmljID09ICJtYWUiKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHBlbmFsdHksIHkgPSBtZWFuLCANCiAgICAgICAgICAgICB5bWluID0gbWVhbiAtIHN0ZF9lcnIsIHltYXggPSBtZWFuICsgc3RkX2VycikpICsgDQogIGdlb21fbGluZXJhbmdlKGFscGhhID0gMC41LCBjb2xvdXIgPSAicmVkIikgKyANCiAgZ2VvbV9wb2ludChjb2xvdXIgPSAicmVkIikgKyANCiAgc2NhbGVfeF9sb2cxMCgpICsgDQogIGxhYnMoeSA9ICJtYWUiLCB4ID0gZXhwcmVzc2lvbihsYW1iZGEpLCANCiAgICAgICB0aXRsZSA9ICJMYXNzbyBSZWdyZXNzc2lvbiBNQUUiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQ0KYGBgDQoNCk5leHQsIHRoZSBMYW1iZGEgdmFsdWUgd2hpY2ggcmVzdWx0cyBpbiBiZXN0IG1vZGVsIHBlcmZvcm1hbmNlIG9uIHRoZSB0cmFpbiBzZXQgaXMgc2VsZWN0ZWQuIEl0IGNhbiBiZSBzZWVuIHRoYXQgYXMgdGhlIFJNU0UgaXMgbW9yZSBzZW5zaXRpdmUgZm9yIGxhcmdlIHJlc2lkdWFscywgdGhlIHN0YW5kYXJkIGVycm9ycyBvZiB0aGVzZSBtZXRyaWNzIGFyZSBsYXJnZXIgY29tcGFyZWQgdG8gdGhlIHN0YW5kYXJkIGVycm9ycyBvZiBtZWFuIGFic29sdXRlIGVycm9yIChNQUUpLiBUaGVyZWZvcmUsIHRoZSBtZWFuIGFic29sdXRlIGVycm9yIGlzIHVzZWQgdG8gc2VsZWN0IHRoZSBiZXN0IG1vZGVsLiANCmBgYHtyfQ0KIyBTaG93IGJlc3QgbW9kZWxzIHdpdGggY29ycmVzcG9uZGluZyBwZW5hbHR5IHZhbHVlcw0KbGFzc29fdHVuZSAlPiUgc2hvd19iZXN0KCJtYWUiKQ0KYGBgDQoNClRoZSBiZXN0IG1vZGVsIGlzIHNlbGVjdGVkIHVzaW5nIHRoZSBvbmUgc3RhbmRhcmQgZXJyb3IgcnVsZSwgd2hlcmUgdGhlIHNpbXBsZXN0IG1vZGVsIHRoYXQgaGFzIE1BRSBpbnNpZGUgb25lIHN0YW5kYXJkIGVycm9yIGZyb20gdGhlIGFic29sdXRlIGJlc3QgbW9kZWwgaXMgY2hvc2VuIHRvIGF2b2lkIG92ZXJmaXR0aW5nLg0KYGBge3J9DQojIFNlbGVjdCBiZXN0IG1vZGVsIGFjY29yZGluZyB0byAxIHN0ZCBlcnJvciBydWxlDQpsYXNzb18xc2VfbW9kZWwgPC0gc2VsZWN0X2J5X29uZV9zdGRfZXJyKGxhc3NvX3R1bmUsIG1ldHJpYyA9ICJtYWUiLCBkZXNjKHBlbmFsdHkpKQ0KbGFzc29fMXNlX21vZGVsDQpgYGANCg0KQXMgY2FuIGJlIHNlZW4sIHRoZSBiZXN0IG1vZGVsIGhhcyBhIHBlbmFsdHkgcGFyYW1ldGVyIG9mIDAuMDA3Lg0KDQpGaW5hbGl6ZSB0aGUgd29ya2Zsb3c6DQpgYGB7ciwgcmVzdWx0cz0naGlkZSd9DQojIEZpbmFsaXplIGxhc3NvIHdmIHdpdGggdGhlIHNlbGVjdGVkIGJlc3QgbW9kZWwNCmxhc3NvX3dmX3R1bmVkIDwtIA0KICBsYXNzb193ZiAlPiUgDQogIGZpbmFsaXplX3dvcmtmbG93KGxhc3NvXzFzZV9tb2RlbCkNCmxhc3NvX3dmX3R1bmVkDQpgYGANCg0KYGBge3J9DQojIFRyYWluIHRoZSB0dW5lZCBtb2RlbCBvbiBhbGwgb2YgdGhlIHRyYWluIGRhdGEgYW5kIHRlc3Qgb24gdGhlIHRlc3QgZGF0YSANCmxhc3NvX2xhc3RfZml0IDwtIGxhc3NvX3dmX3R1bmVkICU+JSANCiAgbGFzdF9maXQoZGF0YV9zcGxpdCwgbWV0cmljcyA9IG1ldHJpY19zZXQobWFlLCBybXNlLCByc3FfdHJhZCkpDQpgYGANCg0KVGhlIHBlcmZvcm1hbmNlIG9uIHRoZSB0ZXN0IHNldCBmb3IgdGhpcyBtb2RlbCBpczoNCmBgYHtyfQ0KIyBDb2xsZWN0IG1ldHJpY3MgZnJvbSB0aGUgbW9kZWwgb24gdGhlIHRlc3Qgc2V0DQpsYXNzb190ZXN0X21ldHJpY3MgPC0gbGFzc29fbGFzdF9maXQgJT4lIGNvbGxlY3RfbWV0cmljcygpDQpsYXNzb190ZXN0X21ldHJpY3MNCmBgYA0KQXMgc2VlbiBhYm92ZSwgdGhlIGZpbmFsIGxhc3NvIG1vZGVsIGhhcyBtZWFuIGFic29sdXRlIGVycm9yIG9mIDAuMjcsIHJvb3QgbWVhbiBzcXVhcmVkIGVycm9yIG9mIDAuMzcgYW5kIFIgc3F1YXJlZCBvbiA0OCw3JSBvbiB0aGUgdGVzdCBkYXRhLg0KDQpUbyBhc3Nlc3MgdGhlIGltcG9ydGFuY2Ugb2YgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMsIG1vZGVsIHBhcmFtZXRlciBlc3RpbWF0ZXMgYXJlIGNhbGN1bGF0ZWQgYmVsb3c6DQpgYGB7cn0NCiMgRml0IHRoZSBtb2RlbCBvbiB0aGUgdHJhaW5pbmcgZGF0YSBhbmQgcHVsbCB0aGUgbW9kZWwgY29lZmZpY2llbnRzIGZvciB0aGUgdmFyaWFibGVzDQpsYXNzb193Zl90dW5lZCAlPiUgZml0KGRhdGFfdHJhaW4pICU+JSBwdWxsX3dvcmtmbG93X2ZpdCgpICU+JSB0aWR5KCkgDQpgYGANCg0KQXMgbGFzc28gcGVyZm9ybXMgc3Vic2V0IHNlbGVjdGlvbiBhdXRvbWF0aWNhbGx5LCBzb21lIHZhcmlhYmxlcyBoYXZlIGEgY29lZmZpY2llbnQgb2YgemVyby4gVGhlcmUgYXJlIG11bHRpcGxlIHZhcmlhYmxlcyB3aXRoIGNvZWZmaWNpZW50IG9mIHplcm8sIHdoaWNoIGltcGxpZXMgdGhhdCB0aGVzZSB2YXJpYWJsZXMgYXJlIGxlc3MgaW1wb3J0YW50IGZvciB0aGUgcHJpY2UgcHJlZGljdGlvbiBvZiBuZXcgQWlyYm5iIGxpc3RpbmdzLiBUaGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGNhbiBiZSBpZGVudGlmaWVkIGJ5IGxvb2tpbmcgYXQgdGhlIGNvZWZmaWNpZW50cyBhcyB3ZWxsLCBhbmQgdGhlIDQgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGFyZSBudW1iZXIgb2YgYWNjb21tb2RhdGVzLCB0aGUgbnVtYmVyIG9mIGRheXMgdGhhdCB0aGUgYWlyYm5iIGlzIGF2YWlsYWJsZSBpbnNpZGUgMzAgZGF5cywgcm9vbSB0eXBlIG9mIGVudGlyZSBob21lIGFwYXJ0bWVudCwgYW5kIGxhc3RseSwgQ2VudHJ1bS1XZXN0IG5laWdoYm91cmhvb2QuDQoNCiMgUmFuZG9tIEZvcmVzdA0KDQojIyMgUmFuZG9tIGZvcmVzdCBzcGVjaWZpY2F0aW9uIA0KDQojIyMjIFJlY2lwZQ0KV2l0aGluIHRoaXMgc2VjdGlvbiwgYSByYW5kb20gZm9yZXN0IHdpbGwgYmUgY3JlYXRlZC4gRmlyc3QgYSBwcmVwcm9jZXNzaW5nIHJlY2lwZSBpcyBjcmVhdGVkLiANClRoZSBpZCB2YXJpYWJsZSBpcyB1cGRhdGVkIHRvIGEgc2VwZXJhdGUgcm9sZSwgaW5zdGVhZCBvZiBiZWluZyBhIHByZWRpY3Rvci4gDQpgYGB7ciBSZWNpcGV9DQojIFNwZWNpZnkgcmVjaXBlDQpyZl9yZWNpcGUgPC0gcmVjaXBlKHByaWNlIH4gLiwgZGF0YSA9IGRhdGFfdHJhaW4pICU+JQ0KICB1cGRhdGVfcm9sZShpZCwgbmV3X3JvbGUgPSAiaWQgdmFyIikNCnJmX3JlY2lwZQ0KYGBgDQoNCiMjIyMgVHVuZSBzcGVjaWZpY2F0aW9ucw0KDQpXaXRoaW4gdGhpcyBzZWN0aW9uIHRoZSB0dW5lIHNwZWNpZmljYXRpb24gYXJlIG1lbnRpb25lZC4gVGhlICptdHJ5KiBpcyB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzIHRoYXQgYXJlIHVzZWQgYXQgZWFjaCBzcGxpdC4gVGhlIGV4YWN0IG10cnkgdmFsdWUgd2lsbCBiZSB0dW5lZCBsYXRlciBvbi4gRGlmZmVyZW50IHZhbHVlcyBmb3IgdHJlZXMgd2hlcmUgdGVzdGVkICgyMDAsIDUwMCAmIDEwMDApLiBJbmNyZWFzaW5nIHRoZSBhbW91bnQgb2YgdHJlZXMgZGlkIG5vdCBoYXZlIG11Y2ggaW1wYWN0IG9uIHRoZSByZXN1bHRzLiBUaGVyZWZvcmUsIGEgdHJlZSBzaXplIG9mIDIwMCBpcyBjaG9zZW4gdG8gc2F2ZSBjb21wdXRhdGlvbmFsIHRpbWUuIA0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCiMgVHVuZSBzcGVjaWZpY2F0aW9uDQpyZl90dW5lX3NwZWMgPC0gcmFuZF9mb3Jlc3QobXRyeSA9IHR1bmUoKSwgdHJlZXMgPSAyMDApICU+JQ0KICBzZXRfZW5naW5lKCJyYW5nZXIiKSAlPiUNCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKQ0KYGBgDQoNClRoZSByZWNpcGUgYW5kIHRoZSBtb2RlbCBhcmUgY29tYmluZWQgaW50byBhIHdvcmtmbG93IHRoYXQgY2FuIGJlIHR1bmVkLg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCiMgV29ya2Zsb3cgY3JlYXRpb24NCnJmX3R1bmVfd2YgPC0gd29ya2Zsb3coKSAlPiUNCiAgYWRkX3JlY2lwZShyZl9yZWNpcGUpICU+JQ0KICBhZGRfbW9kZWwocmZfdHVuZV9zcGVjKQ0KYGBgDQoNCkEgbWV0cmljIHNldCBpcyBzcGVjaWZpZWQsIHdoaWNoIGNhbGN1bGF0ZXMgdGhlICpSb290IE1lYW4gU3F1YXJlIEVycm9yIChSTVNFKSosICp0aGUgTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSogYW5kIHRoZSAqUi1zcXVhcmVkIChyc3FfdHJhZCkqIGlzIGNyZWF0ZWQuIA0KYGBge3IgQ2xhc3MgbWV0cmljc30NCiMgQ2xhc3MgbWV0cmljcyBzcGVjaWZpY2F0aW9uIA0KY2xhc3NfbWV0cmljcyA8LSBtZXRyaWNfc2V0KHJtc2UsIG1hZSwgcnNxX3RyYWQpDQpgYGANCg0KVGhlIGNvbW1hbmQgYmVsbG93IGFsbG93cyB1cyB0byBkbyBjb21wdXRhdGlvbnMgaW4gcGFyYWxsZWwuDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KcmVnaXN0ZXJEb1BhcmFsbGVsKCkNCmBgYA0KDQpUaGUgY29tbWFuZCBncmlkID0gdGliYmxlKG10cnkgPSAxOjMzKSB3YXMgdXRsaXNlZCwgYXMgdGhpcyBhbGxvd3MgdGhlIHJhbmRvbSBmb3Jlc3QgdG8gY29uc2lkZXIgYWxsIG51bWJlciBvZiB2YXJpYWJsZXMgYXQgZWFjaCBzcGxpdC4gQmFzZWQgb24gdGhlIE1BRSBjcml0ZXJpYSwgYSBtdHJ5IG9mIDUsIDYsIDcsIDggJiA5IHdhcyBmb3VuZCBhcyB0aGUgb3B0aW1hbCBzb2x1dGlvbi4gQWZ0ZXJ3YXJkcywgYSBtdHJ5IG9mIGMoMToxMCkpIGlzIHRha2VuIHRoYXQgd2lsbCBpbmNsdWRlIHRoZSBvcHRpbWFsIHZhbHVlcywgYXMgd2VsbCBtdHJ5IHZhbHVlcyBvZiAxIHVwIHVudGlsIDQgYW5kIGEgbXRyeSBvZiAxMC4gVGhpcyBhbGxvd3MgdXMgdG8gc2VlIHRoYXQgdGhlIG10cnkgaXMgaW5pdGlhbGx5IGluY3JlYXNlZCB1cCB1bnRpbCBpdCByZWFjaGVzIGl0cyBvcHRpbWFsIG10cnkgc29sdXRpb24uICANCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQojIERlZmluZSB0aGUgdHVuZSBncmlkIA0KcmZfdHVuZWdyaWQgPC0gdGliYmxlKG10cnkgPSBjKDE6MTApKQ0KYGBgDQoNCmBgYHtyIEdyaWR9DQojIFR1bmUgdGhlIGdyaWQNCnNldC5zZWVkKDEyMzQ1KQ0KcmZfdHVuZV9yZXMgPC0gdHVuZV9ncmlkKA0KICByZl90dW5lX3dmLA0KICByZXNhbXBsZXMgPSBkYXRhX2ZvbGRzLA0KICBncmlkID0gcmZfdHVuZWdyaWQsDQogIG1ldHJpY3MgPSBjbGFzc19tZXRyaWNzDQopDQpyZl90dW5lX3Jlcw0KYGBgDQoNCiMjIyBTZWxlY3RpbmcgdHVuaW5nIHBhcmFtZXRlcnMgDQpgYGB7ciBDb2xsZWN0IG1ldHJpY3N9DQojIENvbGxlY3QgbWV0cmljcw0KcmZfdHVuZV9yZXMgJT4lDQogIGNvbGxlY3RfbWV0cmljcygpDQpgYGANCg0KVGhpcyBwbG90IGlsbHVzdHJhdGVzIHRoZSBiZXN0IG10cnksIGJhc2VkIG9uIHRoZSBjcml0ZXJpYSBvZiB0aGUgTUFFLiBBIGxvd2VyIE1BRSB3b3VsZCBpbmRpY2F0ZSBhIGJldHRlciByZXN1bHQsIGFzIGEgbG93ZXIgdmFsdWUgaW5kaWNhdGVzIGEgbG93ZXIgZXJyb3Igb2YgcHJlZGljdGlvbi4gDQpgYGB7ciBQbG90IE1BRX0NCiMgUGxvdCByZXN1bHRzIGFsbCBtZXRyaWNzDQpyZl90dW5lX3JlcyAlPiUNCiAgY29sbGVjdF9tZXRyaWNzKCkgJT4lDQogIGZpbHRlcigubWV0cmljICVpbiUgYygicm1zZSIsICJtYWUiLCAicnNxX3RyYWQiKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IG10cnksIHkgPSBtZWFuLCB5bWluID0gbWVhbiAtIHN0ZF9lcnIsIHltYXggPSBtZWFuICsgc3RkX2VyciwgDQogICAgICAgICAgICAgY29sb3VyID0gLm1ldHJpYykpICsNCiAgZ2VvbV9lcnJvcmJhcigpICsgDQogIGdlb21fbGluZSgpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZmFjZXRfZ3JpZCgubWV0cmljIH4gLiwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgbGFicyh0aXRsZSA9ICJSYW5kb20gRm9yZXN0IHBlcmZvcm1hbmNlIG1ldHJpY3MiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQ0KYGBgDQoNCmBgYHtyfQ0KIyBQbG90IHRoZSBNQUUgYmFzZWQNCnJmX3R1bmVfcmVzICU+JQ0KICBjb2xsZWN0X21ldHJpY3MoKSAlPiUNCiAgZmlsdGVyKC5tZXRyaWMgPT0gIm1hZSIpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gbXRyeSwgeSA9IG1lYW4sIHltaW4gPSBtZWFuIC0gc3RkX2VyciwgeW1heCA9IG1lYW4gKyBzdGRfZXJyKSkgKw0KICBnZW9tX2Vycm9yYmFyKGNvbG91ciA9ICJyZWQiKSArIA0KICBnZW9tX2xpbmUoY29sb3VyID0gInJlZCIpICsNCiAgZ2VvbV9wb2ludChjb2xvdXIgPSAicmVkIikgKw0KICBsYWJzKHkgPSAibWFlIiwgdGl0bGUgPSAiUmFuZG9tIEZvcmVzdCBwZXJmb3JtYW5jZSBtZXRyaWNzIikgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkNCmBgYA0KDQpUaGlzIGNvbW1hbmQgd2lsbCBzaG93IHRoZSBiZXN0IG10cnkgYmFzZWQgb24gdGhlIE1BRSBtZXRyaWNzLiANCmBgYHtyIEJlc3QgTUFFfQ0KIyBGaW5kIHRoZSBtdHJ5IHdpdGggdGhlIGJlc3QgTUFFDQpyZl90dW5lX3JlcyAlPiUgc2hvd19iZXN0KCJtYWUiKQ0KYGBgDQoNCiMjIyBCZXN0IG1vZGVsIHNlbGVjdGlvbg0KDQpUaGUgYmVzdCBtb2RlbCBiYXNlZCBvbiB0aGUgTUFFIGNyaXRlcmlhIGlzIHNlbGVjdGVkIGFuZCBldmVudHVhbGx5IGZpbmFsaXplZCBpbnRvIHRoZSB3b3JrZmxvdy4gDQpgYGB7ciBCZXN0IG1vZGVsfQ0KIyBCZXN0IG1vZGVsIHNlbGVjdGlvbg0KYmVzdF9ybXNlIDwtIHNlbGVjdF9iZXN0KHJmX3R1bmVfcmVzLCAibWFlIikNCmZpbmFsX3JmIDwtIGZpbmFsaXplX3dvcmtmbG93KHJmX3R1bmVfd2YsIGJlc3Rfcm1zZSkNCmZpbmFsX3JmDQpgYGANCg0KIyMjIyBUZXN0IHNldCBwZXJmb3JtYW5jZQ0KDQpOb3cgd2UgY2FuIHRyYWluIHRoZSBmaW5hbGl6ZWQgd29ya2Zsb3cgb24gb3VyIGVudGlyZSB0cmFpbmluZyBzZXQNCmBgYHtyIEZpbmFsaXNlfQ0KIyBGaW5hbGl6ZSB3b3JrZmxvdyBvbiB0cmFpbmluZyBzZXQNCmZpbmFsX3JlcyA8LSBmaW5hbF9yZiAlPiUNCiAgbGFzdF9maXQoZGF0YV9zcGxpdCwgbWV0cmljcyA9IGNsYXNzX21ldHJpY3MpDQpgYGANCg0KVGhlIHJlc3VsdHMgYmFzZWQgb24gdGhlIHRlc3Qgc2V0IHdpbGwgYmUgDQpgYGB7ciBUZXN0IHJlc3VsdHN9DQojIFNjb3JlIG9uIHRlc3QgZGF0YQ0Kc2V0LnNlZWQoNTQzMjEpDQpmaW5hbF9yZXMgJT4lDQogIGNvbGxlY3RfbWV0cmljcygpDQpgYGANCg0KIyMjIyBWYXJpYWJsZSBpbXBvcnRhbmNlIA0KDQpOb3cgd2UgdHJ5IHRvIGFzc2VzIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlLiBXZSB3aWxsIHJlZml0IHRoZSBtb2RlbCBiYXNlZCBvbiBvdXIgcHJldmlvdXMgdHVuZSBwYXJhbWV0ZXJzLiBXZSBwcmV2aW91c3lseSBmb3VuZCBhbiBvcHRpbWFsIG10cnkgb2YgNywgdGhhdCdzIHdoeSB0aGUgbXRyeSBpcyBzcGVjaWZpZWQgYXMgNy4gSG93ZXZlciwgZG8ga2VlcCBpbiBtaW5kIHRoYXQgYmVjYXVzZSBvZiB0aGUgcmFuZG9tIGVsZW1lbnQgd2l0aGluIGEgcmFuZG9tIGZvcmVzdCwgdGhhdCB0aGlzIGluaXRpYWwgdmFsdWUgbWlnaHQgYWx0ZXIuIFdlIG5vdGljZWQgdGhhdCB0aGUgb3B0aW1hbCBtdHJ5IHN3aXRjaGVzIGJldHdlZW4gNiwgNyAmIDguIA0KYGBge3IgUmVmaXR9DQojIFJlZml0IHRoZSBtb2RlbA0KcmZfbW9kZWxfdmkgPC0gcmFuZF9mb3Jlc3QobXRyeSA9IDcsIHRyZWVzID0gMjAwKSAlPiUNCiAgc2V0X2VuZ2luZSgicmFuZ2VyIiwgaW1wb3J0YW5jZSA9ICJwZXJtdXRhdGlvbiIpDQoNCnJmX3ZpX3dmIDwtIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfbW9kZWwocmZfbW9kZWxfdmkpICU+JSANCiAgYWRkX3JlY2lwZShyZl9yZWNpcGUpDQoNCiMgRml0IHRoZSBtb2RlbCBhZ2Fpbg0Kc2V0LnNlZWQoMTIzNDUpDQpyZl92aV9maXQgPC0gcmZfdmlfd2YgJT4lIGZpdChkYXRhID0gZGF0YV90cmFpbikNCmBgYA0KDQpXZSBjYW4gdXNlIHRoZSByZWZpdHRlZCBtb2RlbCBpbiBvcmRlciB0aGUgZ2F0aGVyIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIA0KYGBge3IgVmFyaWFibGUgaW1wb3J0YW5jZX0NCiMgVmFyaWFibGUgaW1wb3J0YW5jZSANCnJmX3ZpX2ZpdCAlPiUgcHVsbF93b3JrZmxvd19maXQoKSAlPiUgdmkoKQ0KYGBgDQoNClRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIGluZGljYXRlcyB0aGF0IHRoZSBhY2NvbW1vZGF0ZXMsIGJlZHJvb21zIGFuZCByb29tX3R5cGUgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgZm9yIHByZWRpY3RpbmcgdGhlIHByaWNlLiBUaGUgdmFyaWFibGVzIHdoaWNoIGFyZSB0aGUgbGVhc3QgaW1wb3J0YW50IGZvciBwcmVkaWN0aW5nIHRoZSBwcmljZSBhcmUgYmVkX3R5cGUsIHBvb2wsIGFuZCB3aWZpLiBQb29sIGFuZCB3aWZpIGFjdHVhbGx5IGhhdmUgYSBuZWdhdGl2ZSBpbXBvcnRhbmNlLCBidXQgYXMgdGhpcyBpcyBjbG9zZSB0byBhIHZhbHVlIG9mIDAsIGl0IGlzIGNob3NlbiB0byBzdGlsbCBpbmNsdWRlIHRob3NlIHZhcmlhYmxlcy4NCg0KYGBge3J9DQojIFBsb3QgdmFyaWFibGUgaW1wb3J0YW5jZQ0KdmFyX2ltcG9ydGFuY2VfcGxvdCA8LQ0KICByZl92aV9maXQgJT4lDQogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIHZpcChnZW9tID0gInBvaW50IiwgbnVtX2ZlYXR1cmVzID0gMTIpICsNCiAgbGFicyh0aXRsZSA9ICJSYW5kb20gRm9yZXN0IFZhcmlhYmxlIEltcG9ydGFuY2UiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSANCnJmX3ZpX2ZpdA0KDQojIFNhdmUgcGxvdCBmb3IgcHJlc2VudGF0aW9uDQpnZ3NhdmUoInBsb3RzL3JmX3Zhcl9pbXBvcnRhbmNlLnBuZyIsIHBsb3QgPSB2YXJfaW1wb3J0YW5jZV9wbG90LA0KICAgICAgIGhlaWdodCA9IDcgLCB3aWR0aCA9IDEwKQ0KDQpgYGANCg0KIyMgSy1OZWFyZXN0LU5laWdoYm9ycw0KDQojIyMgU2V0IHVwIHR1cm5pbmcgZ3JpZA0KYGBge3J9DQojIEdlbmVyYXRlIHR1bmluZyBncmlkIGZvciBrbm4NCmtubl90dW5lX2dyaWQgPC0gdGliYmxlKG5laWdoYm9ycyA9IDE6NTAqMi0xKQ0KYGBgDQoNCiMjIyBTcGVjaWZ5IGEgd29ya2Zsb3cgDQoNClNvbWV0aGluZyB0aGF0IHNob3VsZCBiZSBub3RlZCBmb3IgdGhpcyByZWNpcGUgaXMgdGhhdCBvbmx5IG51bWVyaWMgdmFyaWFibGVzIGFyZSBpbmNsdWRlZC4gVGhpcyBpcyBkb25lIGZvciB0aGUgcmVhc29uIHRoYXQgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHRyYW5zbGF0ZSB3aXRoIGRpZmZpY3VsdHkgdG8gYSBrLW5lYXJlc3QgbmVpZ2hib3IgYWxnb3JpdGhtLiBUaGUgcHJlbWlzZSBvZiBwcmVkaWN0aW9uIGJhc2VkIG9uIGEgS05OLW1vZGVsIGlzIHRoYXQgaXQgcmVsaWVzIGV4Y2x1c2l2ZWx5IG9uIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHBvaW50cyBpbiB0aGUgZGF0YS4gVGhpcyBkaXN0YW5jZSBpcyBvYnZpb3VzIHdoZW4gaGFuZGxpbmcgbnVtZXJpYyB2YXJpYWJsZXMuIEhvd2V2ZXIsIHdoZW4gZGVhbGluZyB3aXRoIG5vbi1udW1lcmljIHZhbHVlcyBhbmQgdmFyaWFibGVzIHRoaXMgZGlzdGFuY2UgYmV0d2VlbiBkYXRhIHBvaW50cyBjYW5ub3QgZWFzaWx5IGJlIG1vZGVsZWQsIHByb3ZpZGVkIHRoZXkgc2hvdWxkIGJlIG1vZGVsZWQgYXQgYWxsLiAoVGhpcyB3aWxsIGhhdmUgaW1wbGljYXRpb25zIGZvciBkZXRlcm1pbmluZyBwcmVkaWN0aW9ucyBmb3IgaW1wb3J0YW5jZSBhbmQgY29lZmZpY2llbnRzIGZvciB2YXJpYWJsZXMsIHdoaWNoIHdpbGwgYmUgYWRkcmVzc2VkIGF0IHRoZSBlbmQgb2YgdGhlIHNlY3Rpb24gb24gdGhlIEtOTi1tb2RlbCkuDQpgYGB7cn0NCiMgU3BlY2lmeSBtb2RlbCANCmtubl9tb2QgPC0gDQogIG5lYXJlc3RfbmVpZ2hib3IobmVpZ2hib3JzID0gdHVuZSgpKSAlPiUgDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikgJT4lIA0KICBzZXRfZW5naW5lKCJra25uIiwgc2NhbGU9RkFMU0UpDQprbm5fbW9kDQoNCiMgU3BlY2lmeSByZWNpcGUNCmtubl9yZWNpcGUgPC0gDQogIHJlY2lwZShwcmljZSB+IC4sIGRhdGEgPSBkYXRhX3RyYWluKSAlPiUgDQogIHN0ZXBfcm0oYWxsX25vbWluYWwoKSkgJT4lDQogIHVwZGF0ZV9yb2xlKGlkLCBuZXdfcm9sZSA9ICJpZCB2YXIiKSAlPiUgDQogIHN0ZXBfbm9ybWFsaXplKGFsbF9wcmVkaWN0b3JzKCksIC1pZCkNCmtubl9yZWNpcGUNCmBgYA0KDQpUaGUgbm9ybWFsaXphdGlvbiBvZiB0aGUgZGF0YSBpcyBlbnN1cmVkIHRocm91Z2ggdGhlIGZvbGxvd2luZyBjb21tYW5kczoNCmBgYHtyfQ0KIyBDaGVjayBub3JtYWxpemF0aW9uIA0KdHJhaW5fYmFrZWQgPC0ga25uX3JlY2lwZSAlPiUgcHJlcChkYXRhX3RyYWluKSAlPiUgYmFrZShkYXRhX3RyYWluKQ0KdHJhaW5fYmFrZWQgJT4lIGhlYWQoKQ0Kcm91bmQoY29sTWVhbnModHJhaW5fYmFrZWQsIDgpKQ0Kcm91bmQoYXBwbHkodHJhaW5fYmFrZWQsIDIsIHNkKSwgOCkNCnJtKHRyYWluX2Jha2VkKQ0KYGBgDQoNCkJlbG93IHRoZSBpbml0aWFsIHdvcmtmbG93IGZvciB0aGUgay1uZWFyZXN0IG5laWdoYm9ycyBtb2RlbCBpcyBzcGVjaWZpZWQNCmBgYHtyfQ0KIyBTcGVjaWZ5IHdvcmtmbG93DQprbm5fd29ya2Zsb3cgPC0gDQogIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfbW9kZWwoa25uX21vZCkgJT4lIA0KICBhZGRfcmVjaXBlKGtubl9yZWNpcGUpDQprbm5fd29ya2Zsb3cNCmBgYA0KDQojIyMgVHVuaW5nIHRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvdXJzDQoNClRoZSBjb2RlIGJlbG93IHNlcnZlcyB0byBzcGVjaWZ5IHRoZSBhc3Nlc3NtZW50IG1ldHJpY3MgdGhhdCBhcmUgdXNlZC4gTW9yZW92ZXIsIGEgZ3JpZCBzZWFyY2ggaXMgcGVyZm9ybWVkIHVzaW5nIHRoZSB2YWxpZGF0aW9uIHNldHMuIA0KYGBge3IgbWVzc2FnZSA9IEZBTFNFfQ0KIyBTdG9yZSBtZXRyaWNzIGluIHZhcmlhYmxlDQptZXRyaWNzX3JlZyA8LSBtZXRyaWNfc2V0KHJtc2UsIG1hZSwgcnNxX3RyYWQpDQoNCiMgUGVyZm9ybSBncmlkIHNlYXJjaCB1c2luZyB2YWxpZGF0aW9uIHNldHMNCmtubl90dW5lX3JlcyA8LSANCiAga25uX3dvcmtmbG93ICU+JSANCiAgdHVuZV9ncmlkKHJlc2FtcGxlcyA9IGRhdGFfZm9sZHMsDQogICAgICAgICAgICBncmlkID0ga25uX3R1bmVfZ3JpZCwgDQogICAgICAgICAgICBtZXRyaWNzID0gbWV0cmljc19yZWcpIA0KDQojIFBsb3QgcmVzdWx0cyBtZXRyaWNzDQprbm5fbWV0cmljc19wbG90IDwtIA0KICBrbm5fdHVuZV9yZXMgJT4lICBjb2xsZWN0X21ldHJpY3MoKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IG5laWdoYm9ycywgeSA9IG1lYW4pKSArDQogIGdlb21fcG9pbnQoY29sb3VyID0gInJlZCIpICsgZ2VvbV9saW5lKGNvbG91ciA9ICJyZWQiKSArDQogIGZhY2V0X3dyYXAofiAubWV0cmljLCBzY2FsZSA9ICJmcmVlX3kiKSArDQogIGxhYnModGl0bGUgPSAiS05OIFBlcmZvcm1hbmNlIE1ldHJpY3MiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSANCmtubl9tZXRyaWNzX3Bsb3QNCmBgYA0KDQpUaGUgcGxvdCBvdXRwdXQgc2hvd3Mgc29tZSBtZXRyaWNzIHRoYXQgcGxvdCB0aGUgbWVhbiBvZiB0aGUgcGVyZm9ybWFuY2UgbWV0cmljcy4gV2Ugc2hvdWxkIGFpbSBmb3IgdGhlIE1BRSAobWVhbiBhYnNvbHV0ZSBlcnJvcikgYW5kIFJNU0UgKHJvb3QgbWVhbiBzcXVhcmUgZXJyb3IpIHRvIGJlIGFzIGxvdyBhcyBwb3NzaWJsZSwgYW5kIHRoZSByc3FfdHJhZCAoUi1zcXVhcmVkKSB0byBiZSBhcyBoaWdoIGFzIHBvc3NpYmxlLiBXZSB1c2VkIHRoZSBNQUUgbWV0cmljIHRvIGRldGVybWluZSB0aGUgb3B0aW1hbCBrLW5laWdoYm9ycyBmb3Igb3VyIG1vZGVsLCB3aGljaCBhcnJpdmVkIGF0IDUxIG5laWdoYm9ycy4gVGhpcyBjYW4gYmUgcmVhZCBmcm9tIHRoZSBNQUUgZ3JhcGgsIGJ5IGxvb2tpbmcgYXQgdGhlIGNvcnJlc3BvbnNpbmcgay1uZWlnaGJvcnMgZm9yIHRoZSBsb3dlc3QgbWVhbiBvZiBNQUUuIA0KDQpNb3Jlb3ZlciwgZnJvbSB0aGUgbGFzdCBwbG90IHRoZSBlbGJvdyB0cmVuZCBjYW4gc29tZXdoYXQgY2xlYXJseSBiZSBzZWVuOiB0aGUgbWV0cmljcyByZWFjaCB0aGVpciBvcHRpbXVtIHBvaW50IGFmdGVyIHdoaWNoIHRoZSBsZXZlbCBvZmYgYW5kIHNsb3dseSBpbmNyZWFzZSBmb3IgTUFFIGFuZCBSTVNFIGFuZCBkZWNyZWFzZSBmb3IgcnFzX3RyYWQuDQoNClRoZSBtb2RlbCB3aXRoIHRoZSBvcHRpbWFsIG51bWJlciBvZiBrLW5lYXJlc3QgbmVpZ2hib3JzIGNhbiB0aGVuIGJlIHNlbGVjdGVkIGFzIGZvbGxvd3M6DQpgYGB7cn0NCiMgR2VuZXJhdGUgYmVzdCBtb2RlbA0Ka25uX2Jlc3RfbW9kZWwgPC0gc2VsZWN0X2Jlc3Qoa25uX3R1bmVfcmVzLCBtZXRyaWMgPSAibWFlIikNCmBgYA0KDQojIyMgRmluYWxpemUgd29ya2Zsb3cNCg0KQmVsb3cgdGhlIGZpbmFsaXplZCB3b3JrZmxvdyBpcyBtYWRlLCB3aGljaCBhdXRvbWF0aWNhbGx5IHBpY2tzIHRoZSBiZXN0IEtOTi1tb2RlbCBkZWZpbmVkIGFib3ZlICh3aGljaCBpcyBzcGVjaWZpZWQgYnkgdGhlIE1BRSBtZXRyaWMpDQpgYGB7cn0NCiMgRmluYWxpemUgd29ya2Zsb3cNCmtubl93b3JrZmxvd19maW5hbCA8LSANCiAga25uX3dvcmtmbG93ICU+JSANCiAgZmluYWxpemVfd29ya2Zsb3coa25uX2Jlc3RfbW9kZWwpDQprbm5fd29ya2Zsb3dfZmluYWwNCmBgYA0KDQojIyMgTGFzdCBmaXQgDQoNCkEgZmluYWwgd29ya2Zsb3cgY2FuIGJlIHNldCB1cCB0byBjaGVjayB0aGUgZmluYWwgZml0LiBGdXJ0aGVybW9yZSwgdGhlIHBlcmZvcm1hbmNlIG1ldHJpY3MgZm9yIHRoZSBiZXN0IEtOTi1tb2RlbCBhcmUgc2VsZWN0ZWQgYW5kIHB1dCBpbiBhIHRhYmxlLg0KYGBge3J9DQojIFRyYWluIGFuZCB0ZXN0IHRoZSBkYXRhIHNldA0Ka25uX2xhc3RfZml0IDwtIA0KICBrbm5fd29ya2Zsb3dfZmluYWwgJT4lIA0KICBsYXN0X2ZpdChkYXRhX3NwbGl0LCANCiAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpY3NfcmVnKQ0KYGBgDQoNCiMjIyBLTk4gdmFyaWFibGUgaW1wb3J0YW5jZSANCg0KS05OLCBhcyBhIG1ldGhvZCwgZG9lcyBub3QgY29tZSB3aXRoIGEgcHJlZGljdGlvbiBmb3IgdGhlIGltcG9ydGFuY2Ugb3IgY29lZmZpY2llbnRzIG9mIHZhcmlhYmxlcy4gVGhlIHJlYXNvbiBmb3IgdGhpcyBoYXMgdG8gZG8gd2l0aCB0aGUgZmFjdCB0aGF0IHByZWRpY3Rpb24gaW4gYSBrLW5lYXJlc3QgbmVpZ2hib3IgbW9kZWwgcmVsaWVzIGV4Y2x1c2l2ZWx5IG9uIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIGRhdGEgcG9pbnRzLiBXaXRoIHRoaXMgY29tZXMgdGhlIGFkZGVkIGltcGxpY2F0aW9uIHRoYXQgbm8gaW5mb3JtYXRpb24gYWJvdXQgdGhlIHJlbGF0aXZlIGltcG9ydGFuY2Ugb2YgdmFyaWFibGVzIGNhbiBiZSBkZXJpdmVkIGZyb20gaXQuDQoNCg0KIyBQYXJ0IDM6IE1vZGVsIENvbXBhcmlzb24NCg0KSW4gb3JkZXIgdG8gYXNzZXNzIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgdGhyZWUgbW9kZWxzIHRoZSByZXN1bHRzIG9mIHRocmVlIG1ldHJpY3MgYXJlIGNvbXBhcmVkLiANCg0KMS4gUm9vdCBtZWFuIHNxdWFyZWQgZXJyb3IuIFRoZSBvYmplY3RpdmUgaXMgdG8gbWluaW1pemUgdGhlIHJlc3VsdCBvZiB0aGlzIG1ldHJpYy4gQW4gaW1wbGljYXRpb24gaXMgdGhhdCB0aGUgbWV0cmljIGlzIHZlcnkgc2Vuc2l0aXZlIHRvIG9ic2VydmF0aW9ucyB3aXRoIGxhcmdlIGFic29sdXRlIHJlc2lkdWFscy4gDQoyLiBNZWFuIGFic29sdXRlIGVycm9yLiBUaGUgb2JqZWN0aXZlIGlzIHRvIG1pbmltaXplIHRoZSByZXN1bHQgb2YgdGhpcyBtZXRyaWMuIFRoaXMgbWV0cmljIGlzIG1vcmUgcm9idXN0IGFuZCB0aGVyZWZvcmUgbGVzcyBzZW5zaXRpdmUgdG8gb2JzZXJ2YXRpb25zIHdpdGggbGFyZ2UgYWJzb2x1dGUgcmVzaWR1YWxzLiBIb3dldmVyLCBiZWNhdXNlIGl0IHVzZXMgdGhlIG1lYW4sIGl0IGlzIHRoZXJlZm9yZSBub3QgaW5zZW5zaXRpdmUgdG8gc2tld2VkIGRpc3RyaWJ1dGlvbnMuIA0KMy4gUi1zcXVhcmVkLiBBbiBhdHRyYWN0aW9uIGlzIHRoYXQgdGhlIG1ldHJpYyBpcyB1bml0bGVzcyBhbmQgY2FuIHRoZXJlZm9yZSBiZSBjb21wYXJlZCBhY3Jvc3MgbW9kZWxzLiBBIGRvd25zaWRlIGlzIHRoYXQgaXMgbm90IHJvYnVzdCwgc2luY2UgaXQgZXNzZW50aWFsbHkgbWVhc3VyZXMgY29ycmVsYXRpb24gYW5kIG5vdCBhZ3JlZW1lbnQuIA0KDQojIyBBc3Nlc3MgYXBwcm9wcmlhdGVuZXNzIG1ldHJpY3MgDQoNCkluIG9yZGVyIHRvIGNoZWNrIGlmIHRoZSBtZXRyaWNzIGFyZSBhbiBhcHByb3ByaWF0ZSBmaXQsIHRoZSByZXNpZHVhbHMgZGlzdHJpYnV0aW9ucyBvZiB0aGUgbW9kZWxzIGFyZSBwbG90dGVkLiBTaW5jZSB0aGUgcGxvdHMgYmVsb3cgc2hvdyBubyBsYXJnZSBvdXRsaWVycyBhbmQgbm8gc2tld2VkIGRpc3RyaWJ1dGlvbnMsIHRoZXJlIGFyZSBubyBpbXBsaWNhdGlvbnMgZm9yIHJvb3QgbWVhbiBzcXVhcmVkIGVycm9yIGFuZCBtZWFuIGFic29sdXRlIGVycm9yLiANCmBgYHtyIGVjaG8gPSBUUlVFfQ0KIyBHZW5lcmF0ZSBwcmVkaWN0ZWQgdmFsdWVzIGZvciBzYWxlcw0KbGFzc29fdGVzdF9wcmVkcyA8LSANCiAgbGFzc29fd2ZfdHVuZWQgJT4lIA0KICBmaXQoZGF0YSA9IGRhdGFfdHJhaW4pICU+JQ0KICBwcmVkaWN0KGRhdGFfdGVzdCkgJT4lIA0KICBwdWxsKC5wcmVkKQ0KDQojIENyZWF0ZSB0aWJibGUgZm9yIGRpc3RyaWJ1dGlvbiBwbG90DQpsYXNzb19wcmVkIDwtIA0KICB0aWJibGUob2JzZXJ2ZWQgPSBkYXRhX3Rlc3QkcHJpY2UsIA0KICAgICAgICAgcHJlZGljdGVkID0gbGFzc29fdGVzdF9wcmVkcywgDQogICAgICAgICByZXNpZHVhbCA9IG9ic2VydmVkIC0gcHJlZGljdGVkKQ0KDQojIFBsb3QgZGlzdHJpYnV0aW9uIHJlc2lkdWFscw0KbGFzc29fcmVzaWR1YWxfcGxvdCA8LSANCiAgbGFzc29fcHJlZCAlPiUgDQogIGdncGxvdChhZXMoeCA9IHJlc2lkdWFsKSkgKw0KICBnZW9tX2RlbnNpdHkoYncgPSAwLjE1LCBmaWxsID0gInNwcmluZ2dyZWVuIiwgYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9ydWcoKSArDQogIGxhYnModGl0bGUgPSAiTGFzc28gUmVndWxhcml6ZWQgUmVncmVzc2lvbiBEaXN0cmlidXRpb24gUmVzaWR1YWxzIikgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwgMS41KSkNCmxhc3NvX3Jlc2lkdWFsX3Bsb3QNCmBgYA0KDQpgYGB7cn0NCiMgR2VuZXJhdGUgcHJlZGljdGVkIHZhbHVlcyBmb3Igc2FsZXMNCnNldC5zZWVkKDEyMzQ1KQ0KcmZfdGVzdF9wcmVkcyA8LSANCiAgcmZfdmlfd2YgJT4lIA0KICBmaXQoZGF0YSA9IGRhdGFfdHJhaW4pICU+JQ0KICBwcmVkaWN0KGRhdGFfdGVzdCkgJT4lIA0KICBwdWxsKC5wcmVkKQ0KDQojIENyZWF0ZSB0aWJibGUgZm9yIGRpc3RyaWJ1dGlvbiBwbG90DQpyZl9wcmVkIDwtIA0KICB0aWJibGUob2JzZXJ2ZWQgPSBkYXRhX3Rlc3QkcHJpY2UsIA0KICAgICAgICAgcHJlZGljdGVkID0gcmZfdGVzdF9wcmVkcywgDQogICAgICAgICByZXNpZHVhbCA9IG9ic2VydmVkIC0gcHJlZGljdGVkKQ0KDQojIFBsb3QgZGlzdHJpYnV0aW9uIHJlc2lkdWFscw0KcmZfcmVzaWR1YWxfcGxvdCA8LSANCiAgcmZfcHJlZCAlPiUgDQogIGdncGxvdChhZXMoeCA9IHJlc2lkdWFsKSkgKw0KICBnZW9tX2RlbnNpdHkoYncgPSAwLjE1LCBmaWxsID0gInNwcmluZ2dyZWVuIiwgYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9ydWcoKSArDQogIGxhYnModGl0bGUgPSAiUmFuZG9tIEZvcmVzdCBEaXN0cmlidXRpb24gUmVzaWR1YWxzIikgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwgMS41KSkNCnJmX3Jlc2lkdWFsX3Bsb3QNCmBgYA0KDQpgYGB7cn0NCiMgR2VuZXJhdGUgcHJlZGljdGVkIHZhbHVlcyBmb3IgcHJpY2UNCmtubl90ZXN0X3ByZWRzIDwtIA0KICBrbm5fd29ya2Zsb3dfZmluYWwgJT4lIA0KICBmaXQoZGF0YSA9IGRhdGFfdHJhaW4pICU+JQ0KICBwcmVkaWN0KGRhdGFfdGVzdCkgJT4lIA0KICBwdWxsKC5wcmVkKQ0KDQojIENyZWF0ZSB0aWJibGUgZm9yIGRpc3RyaWJ1dGlvbiBwbG90DQprbm5fcHJlZCA8LSANCiAgdGliYmxlKG9ic2VydmVkID0gZGF0YV90ZXN0JHByaWNlLCANCiAgICAgICAgIHByZWRpY3RlZCA9IGtubl90ZXN0X3ByZWRzLCANCiAgICAgICAgIHJlc2lkdWFsID0gb2JzZXJ2ZWQgLSBwcmVkaWN0ZWQpDQoNCiMgUGxvdCBkaXN0cmlidXRpb24gcmVzaWR1YWxzDQprbm5fcmVzaWR1YWxfcGxvdCA8LSANCiAga25uX3ByZWQgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSByZXNpZHVhbCkpICsNCiAgZ2VvbV9kZW5zaXR5KGJ3ID0gMC4xNSwgZmlsbCA9ICJzcHJpbmdncmVlbiIsIGFscGhhID0gMC41KSArDQogIGdlb21fcnVnKCkgKw0KICBsYWJzKHRpdGxlID0gIktOTiBEaXN0cmlidXRpb24gUmVzaWR1YWxzIikgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwgMS41KSkNCmtubl9yZXNpZHVhbF9wbG90DQoNCmBgYA0KDQojIyBBc3Nlc3NtZW50IG1ldHJpY3MNCg0KVGhlIHRhYmxlIGJlbG93IHNob3dzIHJlc3VsdHMgZm9yIHRoZSBtZXRyaWNzIGZvciB0aGUgdGhyZWUgbW9kZWxzLiBXaGVuIGFzc2Vzc2luZyB0aGUgbWV0cmljcyBhbmQgdGhlaXIgb2JqZWN0aXZlcywgdGhlIHJlc3VsdHMgc2hvdyB0aGF0IHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIHBlcmZvcm1zIGJlc3Qgb24gYWxsIG1ldHJpY3MuIFRoZXJlZm9yZSwgdGhlIHJhbmRvbSBmb3Jlc3QgaXMgY29uc2lkZXJlZCB0byBiZSB0aGUgYmVzdCBtb2RlbC4gDQpgYGB7cn0NCiMgR2VuZXJhdGUgdGFibGUgd2l0aCANCmxhc3NvX21ldHJpY3NfY29tcGFyZSA8LSANCiAgbGFzc29fdGVzdF9tZXRyaWNzICU+JSANCiAgc2VsZWN0KC0uZXN0aW1hdG9yKSAlPiUgDQogIG11dGF0ZShtb2RlbCA9ICJsYXNzbyByZWdyZXNzaW9uIikNCnJmX21ldHJpY3NfY29tcGFyZSA8LSANCiAgZmluYWxfcmVzICU+JQ0KICBjb2xsZWN0X21ldHJpY3MoKSAlPiUgDQogIG11dGF0ZShtb2RlbCA9ICJyYW5kb20gZm9yZXN0IikNCmtubl9tZXRyaWNzX2NvbXBhcmUgPC0gDQogIGtubl9sYXN0X2ZpdCAlPiUgDQogIGNvbGxlY3RfbWV0cmljcyAlPiUgDQogIHNlbGVjdCgtLmVzdGltYXRvcikgJT4lIA0KICBtdXRhdGUobW9kZWwgPSAia25uIHJlZyIpDQpsYXNzb19tZXRyaWNzX2NvbXBhcmUgJT4lDQogIGJpbmRfcm93cyhyZl9tZXRyaWNzX2NvbXBhcmUsIGtubl9tZXRyaWNzX2NvbXBhcmUpICU+JSANCiAgc2VsZWN0KC0uZXN0aW1hdG9yKSAlPiUgDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAubWV0cmljLCB2YWx1ZXNfZnJvbSA9IC5lc3RpbWF0ZSkNCmBgYA0KDQojIFBhcnQgNDogUmVjb21tZW5kYXRpb25zDQoNCkZvciB0aGlzIGFzc2lnbm1lbnQgd2UgYXNzZXNzZWQgdGhyZWUgbW9kZWxzIGZvciBwcmVkaWN0aW5nIHByaWNlcyBvbiB0aGUgQWlyYm5iIHBsYXRmb3JtLiBUaGVzZSB0aHJlZSBtb2RlbHMgYXJlIGEgbGFzc28gcmVndWxhcml6ZWQgbGluZWFyIHJlZ3Jlc3Npb24sIGEgcmFuZG9tIGZvcmVzdCBhbmQgZmluYWxseSBhIEstTmVhcmVzdCBOZWlnaGJvdXJzIG1vZGVsLiBUaGUgbW9kZWxzIGFyZSBhc3Nlc3NlZCBvbiB0aHJlZSBkaWZmZXJlbnQgbWV0cmljcywgbmFtZWx5IHRoZSByb290IG1lYW4gc3F1YXJlZCBlcnJvciAoUk1TRSksIHRoZSBtZWFuIHNxdWFyZWQgZXJyb3IgKE1BRSkgYW5kIHRoZSByLXNxdWFyZWQuIFRoZSBwcm9wb3NlZCBhc3Nlc3NtZW50IG1ldHJpY3MgaW5kaWNhdGUgdGhhdCB0aGUgcmFuZG9tIGZvcmVzdCBvdXRwZXJmb3JtcyB0aGUgbGFzc28gcmVndWxhcml6ZWQgbGluZWFyIHJlZ3Jlc3Npb24gYW5kIHRoZSBLLU5lYXJlc3QtTmVpZ2hib3IgaW4gYWxsIHByb3Bvc2VkIG1ldHJpY3MuDQoNCkZ1cnRoZXJtb3JlLCB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZSBvZiB0aGUgcmFuZG9tIGZvcmVzdCBpbmRpY2F0ZXMgd2hpY2ggdmFyaWFibGVzIGxlYWQgdG8gdGhlIGhpZ2hlc3QgaW5jcmVhc2UgaW4gUi1zcXVhcmVkLiBBY2NvbW1vZGF0ZXMsIGJlZHJvb21zIGFuZCByb29tX3R5cGUgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgZm9yIHByZWRpY3RpbmcgdGhlIHByaWNlLiBCZWRfdHlwZSwgcG9vbCwgYW5kIHdpZmkgYXJlIHRoZSBsZWFzdCBpbXBvcnRhbnQgZm9yIHByZWRpY3RpbmcgdGhlIHByaWNlLiBXZSB0aGVyZWZvcmUgYWR2aXNlIGhvc3RzIHRvIHNwZWNpZmljYWxseSBsb29rIGF0IHRoZSB2YXJpYWJsZXMgd2l0aCBhIGhpZ2ggaW1wb3J0YW5jZSwgYXMgdGhlc2UgdmFyaWFibGVzIGhhdmUgdGhlIGhpZ2hlc3QgaW1wYWN0IG9uIHRoZWlyIHByZWRpY3RlZCBwcmljZS4NCg0KQXMgdGhlIG1vZGVsIHBlcmZvcm1hbmNlIG1ldHJpY3Mgd2VyZSB1c2VkIHRvIGNvbXBhcmUgdGhlIG1vZGVscyBhbmQgdG8gc2VsZWN0IHRoZSBiZXN0IG9uZSwgdGhlc2UgbWV0cmljcyBzaG91bGQgbm90IGJlIHVzZWQgdG8gZXN0aW1hdGUgdGhlIGdlbmVyYWxpemF0aW9uIGVycm9yLCBvciBob3cgd2VsbCB0aGUgbW9kZWwgd291bGQgcGVyZm9ybSBvbiBjb21wbGV0ZWx5IG5ldyBkYXRhLiBGb3IgdGhpcyBwdXJwb3NlLCBpdCBpcyByZWNvbW1lbmRlZCB0aGF0IHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIHdpbGwgYmUgZnVydGhlciB0ZXN0ZWQgaW4gYW4gb25saW5lIGVudmlyb25tZW50IGJlZm9yZSBzY2FsaW5nIHVwIGl0cyBpbXBsZW1lbnRhdGlvbi4gQWRkaXRpb25hbGx5LCBpbiBwcmFjdGljZSwgdGhlIG1vZGVsIHdpbGwgcHJvdmlkZSB0aGUgcHJlZGljdGlvbiBpbiBsb2cgcHJpY2VzLCBhbmQgdGhpcyBoYXMgdG8gYmUgY29udmVydGVkIHRvIGV1cm9zLiBGb3IgZXhhbXBsZSwgYSBsb2cgcHJpY2UgcHJlZGljdGlvbiBvZiA0LjA5IHdvdWxkIHJlZmxlY3QgcHJpY2Ugb2Yg4oKsNTkuNzQuDQoNCkhvdyBhcmUgdGhlc2UgZmluZGluZ3Mgb2YgdXNlIHRvIEFpcmJuYj8gRXNzZW50aWFsbHksIHRoaXMgcHJvamVjdCBoYXMgc291Z2h0IHRvIGV4YW1pbmUgdGhyZWUgYXBwcm9hY2hlcyB0byBjb21wZXRpdGl2ZWx5IHNldCBsaXN0aW5nIHByaWNlcyBvZiBBaXJibmIgbGlzdGluZ3MuIENvbnNpZGVyaW5nIG91ciByZXN1bHRzLCB3ZSBhZHZpc2UgQWlyYm5iIHRvIHVzZSBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwgaW4gdGhlaXIgcHJvamVjdHMgdGhhdCBkZWFsIHdpdGggb3B0aW1hbCBwcmljaW5nIG9mIGxpc3RpbmdzLiAgDQoNCg0K